oxkitsune commited on
Commit
ecf8904
·
1 Parent(s): b21a094

Initial commit

Browse files
Files changed (2) hide show
  1. app.py +197 -4
  2. requirements.txt +111 -0
app.py CHANGED
@@ -1,7 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- def greet(name):
4
- return "Hello " + name + "!!"
5
 
6
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
7
- demo.launch()
 
1
+ """
2
+ Demonstrates integrating Rerun visualization with Gradio.
3
+
4
+ Provides example implementations of data streaming, keypoint annotation, and dynamic
5
+ visualization across multiple Gradio tabs using Rerun's recording and visualization capabilities.
6
+ """
7
+
8
+ import math
9
+ import os
10
+ import tempfile
11
+ import time
12
+ import uuid
13
+
14
+ import cv2
15
  import gradio as gr
16
+ import rerun as rr
17
+ import rerun.blueprint as rrb
18
+ from color_grid import build_color_grid
19
+ from gradio_rerun import Rerun
20
+ from gradio_rerun.events import (
21
+ SelectionChange,
22
+ TimelineChange,
23
+ TimeUpdate,
24
+ )
25
+
26
+
27
+ # Whenever we need a recording, we construct a new recording stream.
28
+ # As long as the app and recording IDs remain the same, the data
29
+ # will be merged by the Viewer.
30
+ def get_recording(recording_id: str) -> rr.RecordingStream:
31
+ return rr.RecordingStream(application_id="rerun_example_gradio", recording_id=recording_id)
32
+
33
+
34
+ # A task can directly log to a binary stream, which is routed to the embedded viewer.
35
+ # Incremental chunks are yielded to the viewer using `yield stream.read()`.
36
+ #
37
+ # This is the preferred way to work with Rerun in Gradio since your data can be immediately and
38
+ # incrementally seen by the viewer. Also, there are no ephemeral RRDs to cleanup or manage.
39
+ def streaming_repeated_blur(recording_id: str, img):
40
+ # Here we get a recording using the provided recording id.
41
+ rec = get_recording(recording_id)
42
+ stream = rec.binary_stream()
43
+
44
+ if img is None:
45
+ raise gr.Error("Must provide an image to blur.")
46
+
47
+ blueprint = rrb.Blueprint(
48
+ rrb.Horizontal(
49
+ rrb.Spatial2DView(origin="image/original"),
50
+ rrb.Spatial2DView(origin="image/blurred"),
51
+ ),
52
+ collapse_panels=True,
53
+ )
54
+
55
+ rec.send_blueprint(blueprint)
56
+ rec.set_time("iteration", sequence=0)
57
+ rec.log("image/original", rr.Image(img))
58
+ yield stream.read()
59
+
60
+ blur = img
61
+ for i in range(100):
62
+ rec.set_time("iteration", sequence=i)
63
+
64
+ # Pretend blurring takes a while so we can see streaming in action.
65
+ time.sleep(0.1)
66
+ blur = cv2.GaussianBlur(blur, (5, 5), 0)
67
+ rec.log("image/blurred", rr.Image(blur))
68
+
69
+ # Each time we yield bytes from the stream back to Gradio, they
70
+ # are incrementally sent to the viewer. Make sure to yield any time
71
+ # you want the user to be able to see progress.
72
+ yield stream.read()
73
+
74
+ # Ensure we consume everything from the recording.
75
+ stream.flush()
76
+ yield stream.read()
77
+
78
+
79
+ # In this example the user is able to add keypoints to an image visualized in Rerun.
80
+ # These keypoints are stored in the global state, we use the session id to keep track of which keypoints belong
81
+ # to a specific session (https://www.gradio.app/guides/state-in-blocks).
82
+ #
83
+ # The current session can be obtained by adding a parameter of type `gradio.Request` to your event listener functions.
84
+ Keypoint = tuple[float, float]
85
+ keypoints_per_session_per_sequence_index: dict[str, dict[int, list[Keypoint]]] = {}
86
+
87
+
88
+ def get_keypoints_for_user_at_sequence_index(request: gr.Request, sequence: int) -> list[Keypoint]:
89
+ per_sequence = keypoints_per_session_per_sequence_index[request.session_hash]
90
+ if sequence not in per_sequence:
91
+ per_sequence[sequence] = []
92
+
93
+ return per_sequence[sequence]
94
+
95
+
96
+ def initialize_instance(request: gr.Request) -> None:
97
+ keypoints_per_session_per_sequence_index[request.session_hash] = {}
98
+
99
+
100
+ def cleanup_instance(request: gr.Request) -> None:
101
+ if request.session_hash in keypoints_per_session_per_sequence_index:
102
+ del keypoints_per_session_per_sequence_index[request.session_hash]
103
+
104
+
105
+ # In this function, the `request` and `evt` parameters will be automatically injected by Gradio when this
106
+ # event listener is fired.
107
+ #
108
+ # `SelectionChange` is a subclass of `EventData`: https://www.gradio.app/docs/gradio/eventdata
109
+ # `gr.Request`: https://www.gradio.app/main/docs/gradio/request
110
+ def register_keypoint(
111
+ active_recording_id: str,
112
+ current_timeline: str,
113
+ current_time: float,
114
+ request: gr.Request,
115
+ evt: SelectionChange,
116
+ ):
117
+ if active_recording_id == "":
118
+ return
119
+
120
+ if current_timeline != "iteration":
121
+ return
122
+
123
+ # We can only log a keypoint if the user selected only a single item.
124
+ if len(evt.items) != 1:
125
+ return
126
+ item = evt.items[0]
127
+
128
+ # If the selected item isn't an entity, or we don't have its position, then bail out.
129
+ if item.kind != "entity" or item.position is None:
130
+ return
131
+
132
+ # Now we can produce a valid keypoint.
133
+ rec = get_recording(active_recording_id)
134
+ stream = rec.binary_stream()
135
+
136
+ # We round `current_time` toward 0, because that gives us the sequence index
137
+ # that the user is currently looking at, due to the Viewer's latest-at semantics.
138
+ index = math.floor(current_time)
139
+
140
+ # We keep track of the keypoints per sequence index for each user manually.
141
+ keypoints = get_keypoints_for_user_at_sequence_index(request, index)
142
+ keypoints.append(item.position[0:2])
143
+
144
+ rec.set_time("iteration", sequence=index)
145
+ rec.log(f"{item.entity_path}/keypoint", rr.Points2D(keypoints, radii=2))
146
+
147
+ # Ensure we consume everything from the recording.
148
+ stream.flush()
149
+ yield stream.read()
150
+
151
+
152
+ def track_current_time(evt: TimeUpdate):
153
+ return evt.time
154
+
155
+
156
+ def track_current_timeline_and_time(evt: TimelineChange):
157
+ return evt.timeline, evt.time
158
+
159
+ with gr.Blocks() as demo:
160
+ with gr.Row():
161
+ img = gr.Image(interactive=True, label="Image")
162
+ with gr.Column():
163
+ stream_blur = gr.Button("Stream Repeated Blur")
164
+
165
+ with gr.Row():
166
+ viewer = Rerun(
167
+ streaming=True,
168
+ panel_states={
169
+ "time": "collapsed",
170
+ "blueprint": "hidden",
171
+ "selection": "hidden",
172
+ },
173
+ )
174
+
175
+ # We make a new recording id, and store it in a Gradio's session state.
176
+ recording_id = gr.State(uuid.uuid4())
177
+
178
+ # Also store the current timeline and time of the viewer in the session state.
179
+ current_timeline = gr.State("")
180
+ current_time = gr.State(0.0)
181
+
182
+ # When registering the event listeners, we pass the `recording_id` in as input in order to create
183
+ # a recording stream using that id.
184
+ stream_blur.click(
185
+ # Using the `viewer` as an output allows us to stream data to it by yielding bytes from the callback.
186
+ streaming_repeated_blur,
187
+ inputs=[recording_id, img],
188
+ outputs=[viewer],
189
+ )
190
+ viewer.selection_change(
191
+ register_keypoint,
192
+ inputs=[recording_id, current_timeline, current_time],
193
+ outputs=[viewer],
194
+ )
195
+ viewer.time_update(track_current_time, outputs=[current_time])
196
+ viewer.timeline_change(track_current_timeline_and_time, outputs=[current_timeline, current_time])
197
 
 
 
198
 
199
+ if __name__ == "__main__":
200
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiofiles==24.1.0
2
+ aiohappyeyeballs==2.6.1
3
+ aiohttp==3.11.14
4
+ aiosignal==1.3.2
5
+ annotated-types==0.7.0
6
+ anyio==4.9.0
7
+ asttokens==3.0.0
8
+ async-timeout==5.0.1
9
+ attrs==25.3.0
10
+ Authlib==1.5.2
11
+ certifi==2025.1.31
12
+ cffi==1.17.1
13
+ charset-normalizer==3.4.1
14
+ click==8.0.4
15
+ cryptography==44.0.2
16
+ datasets==3.4.1
17
+ decorator==5.2.1
18
+ dill==0.3.8
19
+ exceptiongroup==1.2.2
20
+ executing==2.2.0
21
+ fastapi==0.115.12
22
+ ffmpy==0.5.0
23
+ filelock==3.18.0
24
+ frozenlist==1.5.0
25
+ fsspec==2024.12.0
26
+ gradio==5.25.0
27
+ gradio_client==1.8.0
28
+ gradio_rerun @ git+https://github.com/rerun-io/gradio-rerun-viewer.git@fc12122ce29473feaa2c722530d8ba35aa2dbdd3
29
+ groovy==0.1.2
30
+ h11==0.14.0
31
+ hf_transfer==0.1.9
32
+ httpcore==1.0.8
33
+ httpx==0.28.1
34
+ huggingface-hub==0.29.3
35
+ idna==3.10
36
+ ipython==8.35.0
37
+ itsdangerous==2.2.0
38
+ jedi==0.19.2
39
+ Jinja2==3.1.6
40
+ markdown-it-py==3.0.0
41
+ MarkupSafe==3.0.2
42
+ matplotlib-inline==0.1.7
43
+ mdurl==0.1.2
44
+ mpmath==1.3.0
45
+ multidict==6.2.0
46
+ multiprocess==0.70.16
47
+ networkx==3.4.2
48
+ numpy==2.2.4
49
+ nvidia-cublas-cu12==12.4.5.8
50
+ nvidia-cuda-cupti-cu12==12.4.127
51
+ nvidia-cuda-nvrtc-cu12==12.4.127
52
+ nvidia-cuda-runtime-cu12==12.4.127
53
+ nvidia-cudnn-cu12==9.1.0.70
54
+ nvidia-cufft-cu12==11.2.1.3
55
+ nvidia-curand-cu12==10.3.5.147
56
+ nvidia-cusolver-cu12==11.6.1.9
57
+ nvidia-cusparse-cu12==12.3.1.170
58
+ nvidia-nccl-cu12==2.21.5
59
+ nvidia-nvjitlink-cu12==12.4.127
60
+ nvidia-nvtx-cu12==12.4.127
61
+ opencv-python==4.11.0.86
62
+ orjson==3.10.16
63
+ packaging==24.2
64
+ pandas==2.2.3
65
+ parso==0.8.4
66
+ pexpect==4.9.0
67
+ pillow==11.1.0
68
+ prompt_toolkit==3.0.50
69
+ propcache==0.3.1
70
+ protobuf==3.20.3
71
+ psutil==5.9.8
72
+ ptyprocess==0.7.0
73
+ pure_eval==0.2.3
74
+ pyarrow==19.0.1
75
+ pycparser==2.22
76
+ pydantic==2.11.3
77
+ pydantic_core==2.33.1
78
+ pydub==0.25.1
79
+ Pygments==2.19.1
80
+ python-dateutil==2.9.0.post0
81
+ python-multipart==0.0.20
82
+ pytz==2025.2
83
+ PyYAML==6.0.2
84
+ requests==2.32.3
85
+ rerun-sdk==0.23.0a2
86
+ rich==14.0.0
87
+ ruff==0.11.5
88
+ safehttpx==0.1.6
89
+ semantic-version==2.10.0
90
+ shellingham==1.5.4
91
+ six==1.17.0
92
+ sniffio==1.3.1
93
+ spaces==0.34.2
94
+ stack-data==0.6.3
95
+ starlette==0.46.1
96
+ sympy==1.13.1
97
+ tomlkit==0.13.2
98
+ torch==2.5.1
99
+ tqdm==4.67.1
100
+ traitlets==5.14.3
101
+ triton==3.1.0
102
+ typer==0.15.2
103
+ typing-inspection==0.4.0
104
+ typing_extensions==4.13.0
105
+ tzdata==2025.2
106
+ urllib3==2.3.0
107
+ uvicorn==0.34.0
108
+ wcwidth==0.2.13
109
+ websockets==15.0.1
110
+ xxhash==3.5.0
111
+ yarl==1.18.3