foxglove-sdk 0.6.2__cp312-cp312-win32.whl → 0.16.6__cp312-cp312-win32.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.
foxglove/cloud.py ADDED
@@ -0,0 +1,61 @@
1
+ from typing import Protocol
2
+
3
+ from ._foxglove_py.websocket import (
4
+ ChannelView,
5
+ Client,
6
+ ClientChannel,
7
+ )
8
+
9
+
10
+ class CloudSinkListener(Protocol):
11
+ """
12
+ A mechanism to register callbacks for handling client message events.
13
+ """
14
+
15
+ def on_subscribe(self, client: Client, channel: ChannelView) -> None:
16
+ """
17
+ Called when a client subscribes to a channel.
18
+
19
+ :param client: The client (id) that sent the message.
20
+ :param channel: The channel (id, topic) that the message was sent on.
21
+ """
22
+ return None
23
+
24
+ def on_unsubscribe(self, client: Client, channel: ChannelView) -> None:
25
+ """
26
+ Called when a client unsubscribes from a channel or disconnects.
27
+
28
+ :param client: The client (id) that sent the message.
29
+ :param channel: The channel (id, topic) that the message was sent on.
30
+ """
31
+ return None
32
+
33
+ def on_client_advertise(self, client: Client, channel: ClientChannel) -> None:
34
+ """
35
+ Called when a client advertises a channel.
36
+
37
+ :param client: The client (id) that sent the message.
38
+ :param channel: The client channel that is being advertised.
39
+ """
40
+ return None
41
+
42
+ def on_client_unadvertise(self, client: Client, client_channel_id: int) -> None:
43
+ """
44
+ Called when a client unadvertises a channel.
45
+
46
+ :param client: The client (id) that is unadvertising the channel.
47
+ :param client_channel_id: The client channel id that is being unadvertised.
48
+ """
49
+ return None
50
+
51
+ def on_message_data(
52
+ self, client: Client, client_channel_id: int, data: bytes
53
+ ) -> None:
54
+ """
55
+ Called when a message is received from a client.
56
+
57
+ :param client: The client (id) that sent the message.
58
+ :param client_channel_id: The client channel id that the message was sent on.
59
+ :param data: The message data.
60
+ """
61
+ return None
@@ -0,0 +1,17 @@
1
+ """
2
+ This module defines types for programmatically constructing Foxglove `layouts <https://docs.foxglove.dev/docs/visualization/layouts>`_.
3
+
4
+ This API is currently experimental and not ready for public use.
5
+ """
6
+
7
+
8
+ class Layout:
9
+ """A Foxglove layout
10
+
11
+ :raises NotImplementedError: This class is currently experimental and not ready for public use.
12
+ """
13
+
14
+ def to_json(self) -> str:
15
+ raise NotImplementedError(
16
+ "This class is currently experimental and not ready for public use."
17
+ )
foxglove/mcap.py ADDED
@@ -0,0 +1,12 @@
1
+ # Re-export these imports
2
+ from ._foxglove_py.mcap import (
3
+ MCAPCompression,
4
+ MCAPWriteOptions,
5
+ MCAPWriter,
6
+ )
7
+
8
+ __all__ = [
9
+ "MCAPCompression",
10
+ "MCAPWriter",
11
+ "MCAPWriteOptions",
12
+ ]
File without changes
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ import pathlib
4
+ from typing import TYPE_CHECKING, Literal
5
+
6
+ import anywidget
7
+ import traitlets
8
+
9
+ if TYPE_CHECKING:
10
+ from ..layouts import Layout
11
+ from .notebook_buffer import NotebookBuffer
12
+
13
+
14
+ class FoxgloveWidget(anywidget.AnyWidget):
15
+ """
16
+ A widget that displays a Foxglove viewer in a notebook.
17
+ """
18
+
19
+ _esm = pathlib.Path(__file__).parent / "static" / "widget.js"
20
+ width = traitlets.Union(
21
+ [traitlets.Int(), traitlets.Enum(values=["full"])], default_value="full"
22
+ ).tag(sync=True)
23
+ height = traitlets.Int(default_value=500).tag(sync=True)
24
+ src = traitlets.Unicode(default_value=None, allow_none=True).tag(sync=True)
25
+ _layout = traitlets.Unicode(default_value=None, allow_none=True).tag(sync=True)
26
+
27
+ def __init__(
28
+ self,
29
+ *,
30
+ buffer: NotebookBuffer,
31
+ width: int | Literal["full"] | None = None,
32
+ height: int | None = None,
33
+ src: str | None = None,
34
+ layout: Layout | None = None,
35
+ ):
36
+ """
37
+ :param buffer: The NotebookBuffer object that contains the data to display in the widget.
38
+ :param width: The width of the widget. Defaults to "full".
39
+ :param height: The height of the widget in pixels. Defaults to 500.
40
+ :param src: The source URL of the Foxglove viewer. Defaults to
41
+ "https://embed.foxglove.dev/".
42
+ """
43
+ super().__init__()
44
+ if width is not None:
45
+ self.width = width
46
+ else:
47
+ self.width = "full"
48
+ if height is not None:
49
+ self.height = height
50
+ if src is not None:
51
+ self.src = src
52
+
53
+ if layout is not None:
54
+ self._layout = layout.to_json()
55
+
56
+ # Callback to get the data to display in the widget
57
+ self._buffer = buffer
58
+ # Keep track of when the widget is ready to receive data
59
+ self._ready = False
60
+ # Pending data to be sent when the widget is ready
61
+ self._pending_data: list[bytes] = []
62
+ self.on_msg(self._handle_custom_msg)
63
+ self.refresh()
64
+
65
+ def refresh(self) -> None:
66
+ """
67
+ Refresh the widget by getting the data from the callback function and sending it
68
+ to the widget.
69
+ """
70
+ data = self._buffer._get_data()
71
+ if not self._ready:
72
+ self._pending_data = data
73
+ else:
74
+ self.send({"type": "update-data"}, data)
75
+
76
+ def _handle_custom_msg(self, msg: dict, buffers: list[bytes]) -> None:
77
+ if msg["type"] == "ready":
78
+ self._ready = True
79
+
80
+ if len(self._pending_data) > 0:
81
+ self.send({"type": "update-data"}, self._pending_data)
82
+ self._pending_data = []
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import uuid
5
+ from tempfile import TemporaryDirectory
6
+ from typing import Literal
7
+
8
+ from mcap.reader import make_reader
9
+
10
+ from .._foxglove_py import Context, open_mcap
11
+ from ..layouts import Layout
12
+ from .foxglove_widget import FoxgloveWidget
13
+
14
+
15
+ class NotebookBuffer:
16
+ """
17
+ A data buffer to collect and manage messages and visualization in Jupyter notebooks.
18
+
19
+ The NotebookBuffer object will buffer all data logged to the provided context. When you
20
+ are ready to visualize the data, you can call the :meth:`show` method to display an embedded
21
+ Foxglove visualization widget. The widget provides a fully-featured Foxglove interface
22
+ directly within your Jupyter notebook, allowing you to explore multi-modal robotics data
23
+ including 3D scenes, plots, images, and more.
24
+ """
25
+
26
+ def __init__(self, *, context: Context | None = None):
27
+ """
28
+ Initialize a new NotebookBuffer for collecting logged messages.
29
+
30
+ :param context: The Context used to log the messages. If no Context is provided, the global
31
+ context will be used. Logged messages will be buffered.
32
+ """
33
+ # We need to keep the temporary directory alive until the writer is closed
34
+ self._temp_directory = TemporaryDirectory()
35
+ self._context = context
36
+ self._files: list[str] = []
37
+ self._create_writer()
38
+
39
+ def show(
40
+ self,
41
+ *,
42
+ width: int | Literal["full"] | None = None,
43
+ height: int | None = None,
44
+ src: str | None = None,
45
+ layout: Layout | None = None,
46
+ ) -> FoxgloveWidget:
47
+ """
48
+ Show the Foxglove viewer. Call this method as the last step of a notebook cell
49
+ to display the viewer.
50
+ """
51
+ widget = FoxgloveWidget(
52
+ buffer=self,
53
+ width=width,
54
+ height=height,
55
+ src=src,
56
+ layout=layout,
57
+ )
58
+ return widget
59
+
60
+ def clear(self) -> None:
61
+ """
62
+ Clear the buffered data.
63
+ """
64
+ self._writer.close()
65
+ # Delete the temporary directory and all its contents
66
+ self._temp_directory.cleanup()
67
+ # Reset files list
68
+ self._files = []
69
+ # Create a new temporary directory
70
+ self._temp_directory = TemporaryDirectory()
71
+ self._create_writer()
72
+
73
+ def _get_data(self) -> list[bytes]:
74
+ """
75
+ Retrieve all collected data.
76
+ """
77
+ # close the current writer
78
+ self._writer.close()
79
+
80
+ if len(self._files) > 1:
81
+ if is_mcap_empty(self._files[-1]):
82
+ # If the last file is empty, remove the last file since it won't add any new data
83
+ # to the buffer
84
+ os.remove(self._files[-1])
85
+ self._files.pop()
86
+ elif is_mcap_empty(self._files[0]):
87
+ # If the first file is empty, remove the first file since it won't add any new data
88
+ # to the buffer
89
+ os.remove(self._files[0])
90
+ self._files.pop(0)
91
+
92
+ # read the content of the files
93
+ contents: list[bytes] = []
94
+ for file_name in self._files:
95
+ with open(file_name, "rb") as f_read:
96
+ contents.append(f_read.read())
97
+
98
+ self._create_writer()
99
+
100
+ return contents
101
+
102
+ def _create_writer(self) -> None:
103
+ random_id = uuid.uuid4().hex[:8]
104
+ file_name = f"{self._temp_directory.name}/log-{random_id}.mcap"
105
+ self._files.append(file_name)
106
+ self._writer = open_mcap(path=file_name, context=self._context)
107
+
108
+
109
+ def is_mcap_empty(file_name: str) -> bool:
110
+ with open(file_name, "rb") as f_read:
111
+ iter = make_reader(f_read).iter_messages()
112
+ is_empty = next(iter, None) is None
113
+
114
+ return is_empty
@@ -0,0 +1 @@
1
+ var g=EventTarget,f="https://embed.foxglove.dev/",w="foxglove.default-layout";function y(){let t,e;return{promise:new Promise((a,i)=>{t=a,e=i}),resolve:t,reject:e}}var d=class extends g{#e;#a;#d;#r=!1;#n;#t={dataSource:void 0,selectLayout:void 0,extension:[]};#s;#i=!1;constructor(e){super();let{parent:s,src:a,orgSlug:i,initialDataSource:o,initialLayout:r,initialLayoutParams:n,initialExtensions:l,colorScheme:h="auto",embeddedViewer:u}=e;this.#d=i,this.#n=y();let c=a??f;try{this.#a=new URL(c)}catch{throw new Error(`[FoxgloveViewer] Invalid server URL: ${c}`)}u!=null&&this.#a.searchParams.set("embeddedViewer",u),window.addEventListener("message",this.#h),o&&this.setDataSource(o),r!=null&&n==null&&this.setLayoutData(r),n!=null&&this.selectLayout(n),l&&this.installExtensions(l),this.#e=document.createElement("iframe"),this.#e.src=this.#a.href,this.#e.title="Foxglove",this.#e.allow="cross-origin-isolated",this.#e.style.width="100%",this.#e.style.height="100%",this.#e.style.border="none",this.setColorScheme(h),s.appendChild(this.#e)}setDataSource(e){this.#l({type:"set-data-source",payload:e})}selectLayout(e){this.#l({type:"select-layout",payload:e})}async getLayout(){return this.#s||(this.#s=y(),await this.#n.promise,this.#o({type:"get-layout"})),await this.#s.promise}setLayoutData(e){this.selectLayout({storageKey:w,opaqueLayout:e,force:!0})}installExtensions(e){this.#l({type:"install-extension",payload:e})}isReady(){return this.#r}destroy(){this.#i=!0,this.#e.remove(),window.removeEventListener("message",this.#h)}isDestroyed(){return this.#i}setColorScheme(e){this.#e.style.colorScheme=e==="auto"?"normal":e}#l(e){switch(e.type){case"install-extension":this.#t.extension.push(e);break;case"set-data-source":this.#t.dataSource=e;break;case"select-layout":this.#t.selectLayout=e;break}this.#r&&this.#o(e)}#o(e){if(this.#i){console.warn("[FoxgloveViewer] Unable to post command. Frame has been destroyed.");return}v(this.#e.contentWindow,"Invariant: iframe should be loaded."),this.#e.contentWindow.postMessage(e,this.#a.origin)}#h=e=>{let s=new URL(e.origin);if(!(e.source!==this.#e.contentWindow||s.origin!==this.#a.origin)){if(this.#i){console.warn("[FoxgloveViewer] Unable to handle message. Frame has been destroyed.");return}switch(e.data.type){case"foxglove-origin-request":this.#o({type:"origin-ack"});break;case"foxglove-handshake-request":this.#r=!0,this.#n.resolve(),this.#o({type:"handshake-ack",payload:{orgSlug:this.#d,initialDataSource:this.#t.dataSource?.payload,initialLayoutParams:this.#t.selectLayout?.payload,initialExtensions:this.#t.extension.flatMap(a=>a.payload)}});break;case"foxglove-handshake-complete":this.dispatchEvent(new Event("ready"));break;case"foxglove-error":this.dispatchEvent(new CustomEvent("error",{detail:e.data.payload}));break;case"foxglove-layout-data":this.#s?(this.#s.resolve(e.data.payload),this.#s=void 0):console.warn("[FoxgloveViewer] Received layout but getLayout was not called.");break;default:console.warn("[FoxgloveViewer] Unhandled message type:",e.data);break}}}};function v(t,e="no additional info provided"){if(!t)throw new Error("Assertion Error: "+e)}var m="foxglove-notebook-default-layout";function p(t){return{storageKey:m,force:!0,layout:t?JSON.parse(t):void 0}}function x({model:t,el:e}){let s=document.createElement("div"),a=t.get("_layout"),i=new d({parent:s,embeddedViewer:"Python",src:t.get("src"),orgSlug:void 0,initialLayoutParams:p(a)});i.addEventListener("ready",()=>{t.send({type:"ready"})}),t.on("msg:custom",(o,r)=>{if(o.type==="update-data"){let n=r.map((l,h)=>new File([l.buffer],`data-${h}.mcap`));i.setDataSource({type:"file",file:n})}}),s.style.width=t.get("width")==="full"?"100%":`${t.get("width")}px`,s.style.height=`${t.get("height")}px`,t.on("change:width",()=>{s.style.width=t.get("width")==="full"?"100%":`${t.get("width")}px`}),t.on("change:height",()=>{s.style.height=`${t.get("height")}px`}),t.on("change:_layout",()=>{let o=t.get("_layout"),r=p(o);i.selectLayout(r)}),e.appendChild(s)}var R={render:x};export{R as default};
@@ -1,149 +1,166 @@
1
- """
2
- This module contains the definitions of the well-known Foxglove schemas for logging.
3
-
4
- Log messages to a corresponding channel type from :py:mod:`foxglove.channels`.
5
- """
6
-
7
- # Generated by https://github.com/foxglove/foxglove-sdk
8
-
9
- from typing import Union
10
-
11
- from foxglove._foxglove_py.schemas import (
12
- ArrowPrimitive,
13
- CameraCalibration,
14
- CircleAnnotation,
15
- Color,
16
- CompressedImage,
17
- CompressedVideo,
18
- CubePrimitive,
19
- CylinderPrimitive,
20
- Duration,
21
- FrameTransform,
22
- FrameTransforms,
23
- GeoJson,
24
- Grid,
25
- ImageAnnotations,
26
- KeyValuePair,
27
- LaserScan,
28
- LinePrimitive,
29
- LinePrimitiveLineType,
30
- LocationFix,
31
- LocationFixPositionCovarianceType,
32
- Log,
33
- LogLevel,
34
- ModelPrimitive,
35
- PackedElementField,
36
- PackedElementFieldNumericType,
37
- Point2,
38
- Point3,
39
- PointCloud,
40
- PointsAnnotation,
41
- PointsAnnotationType,
42
- Pose,
43
- PoseInFrame,
44
- PosesInFrame,
45
- Quaternion,
46
- RawImage,
47
- SceneEntity,
48
- SceneEntityDeletion,
49
- SceneEntityDeletionType,
50
- SceneUpdate,
51
- SpherePrimitive,
52
- TextAnnotation,
53
- TextPrimitive,
54
- Timestamp,
55
- TriangleListPrimitive,
56
- Vector2,
57
- Vector3,
58
- )
59
-
60
- FoxgloveSchema = Union[
61
- ArrowPrimitive,
62
- CameraCalibration,
63
- CircleAnnotation,
64
- Color,
65
- CompressedImage,
66
- CompressedVideo,
67
- CubePrimitive,
68
- CylinderPrimitive,
69
- FrameTransform,
70
- FrameTransforms,
71
- GeoJson,
72
- Grid,
73
- ImageAnnotations,
74
- KeyValuePair,
75
- LaserScan,
76
- LinePrimitive,
77
- LocationFix,
78
- Log,
79
- ModelPrimitive,
80
- PackedElementField,
81
- Point2,
82
- Point3,
83
- PointCloud,
84
- PointsAnnotation,
85
- Pose,
86
- PoseInFrame,
87
- PosesInFrame,
88
- Quaternion,
89
- RawImage,
90
- SceneEntity,
91
- SceneEntityDeletion,
92
- SceneUpdate,
93
- SpherePrimitive,
94
- TextAnnotation,
95
- TextPrimitive,
96
- TriangleListPrimitive,
97
- Vector2,
98
- Vector3,
99
- ]
100
-
101
- __all__ = [
102
- "FoxgloveSchema",
103
- "ArrowPrimitive",
104
- "CameraCalibration",
105
- "CircleAnnotation",
106
- "Color",
107
- "CompressedImage",
108
- "CompressedVideo",
109
- "CubePrimitive",
110
- "CylinderPrimitive",
111
- "Duration",
112
- "FrameTransform",
113
- "FrameTransforms",
114
- "GeoJson",
115
- "Grid",
116
- "ImageAnnotations",
117
- "KeyValuePair",
118
- "LaserScan",
119
- "LinePrimitive",
120
- "LinePrimitiveLineType",
121
- "LocationFix",
122
- "LocationFixPositionCovarianceType",
123
- "Log",
124
- "LogLevel",
125
- "ModelPrimitive",
126
- "PackedElementField",
127
- "PackedElementFieldNumericType",
128
- "Point2",
129
- "Point3",
130
- "PointCloud",
131
- "PointsAnnotation",
132
- "PointsAnnotationType",
133
- "Pose",
134
- "PoseInFrame",
135
- "PosesInFrame",
136
- "Quaternion",
137
- "RawImage",
138
- "SceneEntity",
139
- "SceneEntityDeletion",
140
- "SceneEntityDeletionType",
141
- "SceneUpdate",
142
- "SpherePrimitive",
143
- "TextAnnotation",
144
- "TextPrimitive",
145
- "Timestamp",
146
- "TriangleListPrimitive",
147
- "Vector2",
148
- "Vector3",
149
- ]
1
+ """
2
+ This module contains the definitions of the well-known Foxglove schemas for logging.
3
+
4
+ Log messages to a corresponding channel type from :py:mod:`foxglove.channels`.
5
+
6
+ Note that the schema classes are currently immutable and do not expose
7
+ getters and setters for their fields. This is a limitation we plan to address in the future.
8
+ """
9
+
10
+ # Generated by https://github.com/foxglove/foxglove-sdk
11
+
12
+ from typing import Union
13
+
14
+ from foxglove._foxglove_py.schemas import (
15
+ ArrowPrimitive,
16
+ CameraCalibration,
17
+ CircleAnnotation,
18
+ Color,
19
+ CompressedImage,
20
+ CompressedVideo,
21
+ CubePrimitive,
22
+ CylinderPrimitive,
23
+ Duration,
24
+ FrameTransform,
25
+ FrameTransforms,
26
+ GeoJson,
27
+ Grid,
28
+ ImageAnnotations,
29
+ KeyValuePair,
30
+ LaserScan,
31
+ LinePrimitive,
32
+ LinePrimitiveLineType,
33
+ LocationFix,
34
+ LocationFixes,
35
+ LocationFixPositionCovarianceType,
36
+ Log,
37
+ LogLevel,
38
+ ModelPrimitive,
39
+ PackedElementField,
40
+ PackedElementFieldNumericType,
41
+ Point2,
42
+ Point3,
43
+ Point3InFrame,
44
+ PointCloud,
45
+ PointsAnnotation,
46
+ PointsAnnotationType,
47
+ Pose,
48
+ PoseInFrame,
49
+ PosesInFrame,
50
+ Quaternion,
51
+ RawAudio,
52
+ RawImage,
53
+ SceneEntity,
54
+ SceneEntityDeletion,
55
+ SceneEntityDeletionType,
56
+ SceneUpdate,
57
+ SpherePrimitive,
58
+ TextAnnotation,
59
+ TextPrimitive,
60
+ Timestamp,
61
+ TriangleListPrimitive,
62
+ Vector2,
63
+ Vector3,
64
+ VoxelGrid,
65
+ )
66
+
67
+ FoxgloveSchema = Union[
68
+ ArrowPrimitive,
69
+ CameraCalibration,
70
+ CircleAnnotation,
71
+ Color,
72
+ CompressedImage,
73
+ CompressedVideo,
74
+ CubePrimitive,
75
+ CylinderPrimitive,
76
+ Duration,
77
+ FrameTransform,
78
+ FrameTransforms,
79
+ GeoJson,
80
+ Grid,
81
+ ImageAnnotations,
82
+ KeyValuePair,
83
+ LaserScan,
84
+ LinePrimitive,
85
+ LocationFix,
86
+ LocationFixes,
87
+ Log,
88
+ ModelPrimitive,
89
+ PackedElementField,
90
+ Point2,
91
+ Point3,
92
+ Point3InFrame,
93
+ PointCloud,
94
+ PointsAnnotation,
95
+ Pose,
96
+ PoseInFrame,
97
+ PosesInFrame,
98
+ Quaternion,
99
+ RawAudio,
100
+ RawImage,
101
+ SceneEntity,
102
+ SceneEntityDeletion,
103
+ SceneUpdate,
104
+ SpherePrimitive,
105
+ TextAnnotation,
106
+ TextPrimitive,
107
+ Timestamp,
108
+ TriangleListPrimitive,
109
+ Vector2,
110
+ Vector3,
111
+ VoxelGrid,
112
+ ]
113
+
114
+ __all__ = [
115
+ "FoxgloveSchema",
116
+ "ArrowPrimitive",
117
+ "CameraCalibration",
118
+ "CircleAnnotation",
119
+ "Color",
120
+ "CompressedImage",
121
+ "CompressedVideo",
122
+ "CubePrimitive",
123
+ "CylinderPrimitive",
124
+ "Duration",
125
+ "FrameTransform",
126
+ "FrameTransforms",
127
+ "GeoJson",
128
+ "Grid",
129
+ "ImageAnnotations",
130
+ "KeyValuePair",
131
+ "LaserScan",
132
+ "LinePrimitive",
133
+ "LinePrimitiveLineType",
134
+ "LocationFix",
135
+ "LocationFixPositionCovarianceType",
136
+ "LocationFixes",
137
+ "Log",
138
+ "LogLevel",
139
+ "ModelPrimitive",
140
+ "PackedElementField",
141
+ "PackedElementFieldNumericType",
142
+ "Point2",
143
+ "Point3",
144
+ "Point3InFrame",
145
+ "PointCloud",
146
+ "PointsAnnotation",
147
+ "PointsAnnotationType",
148
+ "Pose",
149
+ "PoseInFrame",
150
+ "PosesInFrame",
151
+ "Quaternion",
152
+ "RawAudio",
153
+ "RawImage",
154
+ "SceneEntity",
155
+ "SceneEntityDeletion",
156
+ "SceneEntityDeletionType",
157
+ "SceneUpdate",
158
+ "SpherePrimitive",
159
+ "TextAnnotation",
160
+ "TextPrimitive",
161
+ "Timestamp",
162
+ "TriangleListPrimitive",
163
+ "Vector2",
164
+ "Vector3",
165
+ "VoxelGrid",
166
+ ]