MarcSkovMadsen commited on
Commit
0be8eeb
1 Parent(s): edbef60

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +19 -4
  2. index.html +299 -18
  3. index.js +524 -0
  4. index.py +421 -0
README.md CHANGED
@@ -1,11 +1,26 @@
1
  ---
2
  title: Video Stream
3
- emoji: 📈
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: static
7
  pinned: false
8
  license: mit
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Video Stream
3
+ emoji: 📷
4
+ colorFrom: indigo
5
+ colorTo: pink
6
  sdk: static
7
  pinned: false
8
  license: mit
9
  ---
10
 
11
+ See [video_stream](https://awesome-panel.org/resources/video_stream/) by [awesome-panel.org](https://awesome-panel.org) for more info.
12
+
13
+ ## Serve
14
+
15
+ ```python
16
+ panel serve index.py --autoreload
17
+ ```
18
+
19
+ ## Convert to Pyodide
20
+
21
+ ```bash
22
+ panel convert index.py --to pyodide-worker
23
+ ```
24
+
25
+ Please note you will currently have to manually remove `PIL` and change `skimage` to `scikit-image`
26
+ in the `index.js` file ([Issue #6170](https://github.com/holoviz/panel/issues/6170)).
index.html CHANGED
@@ -1,19 +1,300 @@
1
  <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
+ <html lang="en" >
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>VideoStream with ScikitImage</title>
6
+ <link rel="apple-touch-icon" sizes="180x180" href="https://cdn.holoviz.org/panel/1.3.6/dist/images/apple-touch-icon.png">
7
+ <link rel="icon" href="https://cdn.holoviz.org/panel/1.3.6/dist/images/favicon.ico" type="">
8
+ <meta name="name" content="VideoStream with ScikitImage">
9
+ <style>
10
+ html, body {
11
+ display: flow-root;
12
+ box-sizing: border-box;
13
+ height: 100%;
14
+ margin: 0;
15
+ padding: 0;
16
+ }
17
+ </style>
18
+ <style>
19
+
20
+ .mdc-drawer {background: var(--light-bg-color) !important;}
21
+ </style>
22
+
23
+ <style type="text/css">
24
+ :host(.pn-loading),
25
+ .pn-loading {
26
+ overflow: hidden;
27
+ }
28
+
29
+ :host(.pn-loading):before,
30
+ .pn-loading:before {
31
+ position: absolute;
32
+ height: 100%;
33
+ width: 100%;
34
+ content: '';
35
+ z-index: 1000;
36
+ background-color: rgb(255, 255, 255, 0.5);
37
+ border-color: lightgray;
38
+ background-repeat: no-repeat;
39
+ background-position: center;
40
+ background-size: auto 50%;
41
+ border-width: 1px;
42
+ cursor: progress;
43
+ }
44
+
45
+ :host(.pn-loading) .pn-loading-msg,
46
+ .pn-loading .pn-loading-msg {
47
+ position: absolute;
48
+ top: 72%;
49
+ font-size: 2em;
50
+ color: black;
51
+ width: 100%;
52
+ text-align: center;
53
+ }
54
+
55
+
56
+ :host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {
57
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=");
58
+ background-size: auto calc(min(50%, 400px));
59
+ }
60
+ </style><script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-3.3.2.min.js"></script>
61
+ <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.2.min.js"></script>
62
+ <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.2.min.js"></script>
63
+ <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.2.min.js"></script>
64
+ <script type="text/javascript" src="https://cdn.holoviz.org/panel/1.3.6/dist/panel.min.js"></script>
65
+
66
+ <script type="text/javascript">
67
+ Bokeh.set_log_level("info");
68
+ </script>
69
+ <!-- Template CSS -->
70
+ <link rel="stylesheet" href="https://cdn.holoviz.org/panel/1.3.6/dist/bundled/material-components-web@7.0.0/dist/material-components-web.min.css">
71
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
72
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons&display=block">
73
+ <link rel="stylesheet" href="https://cdn.holoviz.org/panel/1.3.6/dist/bundled/theme/default.css">
74
+ <link rel="stylesheet" href="https://cdn.holoviz.org/panel/1.3.6/dist/bundled/theme/material_variables.css">
75
+ <link rel="stylesheet" href="https://cdn.holoviz.org/panel/1.3.6/dist/bundled/materialtemplate/material.css">
76
+ <style type="text/css">
77
+
78
+ :host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {
79
+ background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=");
80
+ background-size: auto calc(min(50%, 400px));
81
+ }
82
+ </style>
83
+
84
+ <style>
85
+ :root {
86
+ --header-background: var(--design-primary-color, var(--panel-primary-color));
87
+ --header-color: var(--design-primary-text-color, var(--panel-on-primary-color));
88
+ --sidebar-width: 370px;
89
+ }
90
+ #header {
91
+ --mdc-theme-background: var(--header-background);
92
+ --mdc-theme-on-background: var(--header-color);
93
+ background-color: var(--header-background);
94
+ color: var(--header-color);
95
+ }
96
+ .mdc-drawer {
97
+ width: var(--sidebar-width) !important;
98
+ }
99
+ .mdc-drawer.mdc-drawer--open:not(.mdc-drawer--closing)+.mdc-drawer-app-content {
100
+ margin-left: var(--sidebar-width) !important;
101
+ }
102
+ </style>
103
+
104
+ <!-- Template JS -->
105
+ <script src="https://cdn.holoviz.org/panel/1.3.6/dist/bundled/material-components-web@7.0.0/dist/material-components-web.min.js"></script>
106
+ </head>
107
+ <body class="pn-loading pn-arc">
108
+ <div class="mdc-typography" id="container">
109
+ <header class="mdc-top-app-bar app-bar mdc-top-app-bar--fixed" id="header">
110
+ <div class="mdc-top-app-bar__row">
111
+ <section class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start">
112
+ <button class="material-icons mdc-top-app-bar__navigation-icon mdc-icon-button">menu</button>
113
+ <div class="title-bar">
114
+ <span class="mdc-top-app-bar__title app-header">
115
+ <a class="title" href="/" >&nbsp;Awesome Panel</a><span class="title">&nbsp;-</span><a class="title" href="" >&nbsp;VideoStream with ScikitImage</a> </span>
116
+ <div id="header-items">
117
+ </div>
118
+ </div>
119
+ </section>
120
+ <div class="pn-busy-container">
121
+ <div id="c489da48-6f7a-4f64-bb54-4971c884670a" data-root-id="p1016" style="display: contents;"></div>
122
+ </div>
123
+ </div>
124
+ </header>
125
+
126
+ <aside class="mdc-drawer mdc-top-app-bar--fixed-adjust mdc-drawer--dismissible mdc-drawer--open" id="sidebar">
127
+ <div class="mdc-drawer__content">
128
+ <div class="mdc-list">
129
+ <div id="c5ca8963-30c7-446b-9af0-4a5fb9a34bfc" data-root-id="p1028" style="display: contents;"></div>
130
+ </div>
131
+ </div>
132
+ </aside>
133
+
134
+ <div class="mdc-drawer-app-content mdc-top-app-bar--fixed-adjust">
135
+ <main class="main-content" id="main">
136
+ <div id="b7e25cf5-128e-499f-ac93-6fb5a5859569" data-root-id="p1017" style="display: contents;"></div>
137
+ </main>
138
+
139
+ <div id="pn-Modal" class="mdc-dialog">
140
+ <div class="mdc-dialog__container">
141
+ <div class="mdc-dialog__surface" role="alertdialog" aria-modal="true">
142
+ <div class="mdc-dialog__content" id="my-dialog-content">
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+
149
+ <script type="text/javascript">
150
+ var drawer = mdc.drawer.MDCDrawer.attachTo(document.querySelector('.mdc-drawer'));
151
+ var topAppBar = mdc.topAppBar.MDCTopAppBar.attachTo(document.getElementById('header'));
152
+ topAppBar.setScrollTarget(document.getElementById('main'));
153
+ topAppBar.listen('MDCTopAppBar:nav', function() {
154
+ drawer.open = !drawer.open;
155
+ });
156
+
157
+ var modal_el = document.getElementById("pn-Modal");
158
+ var modal = new mdc.dialog.MDCDialog(modal_el);
159
+
160
+ window.onclick = function(event) {
161
+ if (event.target == modal_el) {
162
+ modal.close();
163
+ }
164
+ }
165
+ </script>
166
+
167
+ <div id="b6ab6366-6c22-4f7f-b812-f2e1a390e026" data-root-id="p1009" style="display: contents;"></div>
168
+ <div id="bf2a4026-7554-4e87-bfde-2673dedfcd49" data-root-id="p1012" style="display: contents;"></div>
169
+ <div id="c861e5fb-b514-4e68-8b16-9174f1ba9a9c" data-root-id="p1002" style="display: contents;"></div>
170
+ <div id="ade4f51b-e294-42a9-a3e7-478d065f730b" data-root-id="p1013" style="display: contents;"></div>
171
+ </div>
172
+
173
+ <script type="text/javascript">
174
+ const pyodideWorker = new Worker("./index.js");
175
+ pyodideWorker.busy = false
176
+ pyodideWorker.queue = []
177
+
178
+ function send_change(jsdoc, event) {
179
+ if (event.setter_id != null && event.setter_id == 'py') {
180
+ return
181
+ } else if (pyodideWorker.busy && event.model && event.attr) {
182
+ let events = []
183
+ for (const old_event of pyodideWorker.queue) {
184
+ if (!(old_event.model === event.model && old_event.attr === event.attr)) {
185
+ events.push(old_event)
186
+ }
187
+ }
188
+ events.push(event)
189
+ pyodideWorker.queue = events
190
+ return
191
+ }
192
+ const patch = jsdoc.create_json_patch([event])
193
+ pyodideWorker.busy = true
194
+ pyodideWorker.postMessage({type: 'patch', patch: patch})
195
+ }
196
+
197
+ pyodideWorker.onmessage = async (event) => {
198
+ const msg = event.data
199
+
200
+ const body = document.getElementsByTagName('body')[0]
201
+ const loading_msgs = document.getElementsByClassName('pn-loading-msg')
202
+ if (msg.type === 'idle') {
203
+ if (pyodideWorker.queue.length) {
204
+ const patch = pyodideWorker.jsdoc.create_json_patch(pyodideWorker.queue)
205
+ pyodideWorker.busy = true
206
+ pyodideWorker.queue = []
207
+ pyodideWorker.postMessage({type: 'patch', patch: patch})
208
+ } else {
209
+ pyodideWorker.busy = false
210
+ }
211
+ } else if (msg.type === 'status') {
212
+ let loading_msg
213
+ if (loading_msgs.length) {
214
+ loading_msg = loading_msgs[0]
215
+ } else if (body.classList.contains('pn-loading')) {
216
+ loading_msg = document.createElement('div')
217
+ loading_msg.classList.add('pn-loading-msg')
218
+ body.appendChild(loading_msg)
219
+ }
220
+ if (loading_msg != null) {
221
+ loading_msg.innerHTML = msg.msg
222
+ }
223
+ } else if (msg.type === 'render') {
224
+ const docs_json = JSON.parse(msg.docs_json)
225
+ const render_items = JSON.parse(msg.render_items)
226
+ const root_ids = JSON.parse(msg.root_ids)
227
+
228
+ // Remap roots in message to element IDs
229
+ const root_els = document.querySelectorAll('[data-root-id]')
230
+ const data_roots = []
231
+ for (const el of root_els) {
232
+ el.innerHTML = ''
233
+ data_roots.push([el.getAttribute('data-root-id'), el.id])
234
+ }
235
+ data_roots.sort((a, b) => a[0]<b[0] ? -1: 1)
236
+ const roots = {}
237
+ for (let i=0; i<data_roots.length; i++) {
238
+ roots[root_ids[i]] = data_roots[i][1]
239
+ }
240
+ render_items[0]['roots'] = roots
241
+ render_items[0]['root_ids'] = root_ids
242
+
243
+ // Embed content
244
+ const [views] = await Bokeh.embed.embed_items(docs_json, render_items)
245
+
246
+ // Remove loading spinner and message
247
+ body.classList.remove("pn-loading", "arc")
248
+ for (const loading_msg of loading_msgs) {
249
+ loading_msg.remove()
250
+ }
251
+
252
+ // Setup bi-directional syncing
253
+ pyodideWorker.jsdoc = jsdoc = [...views.roots.values()][0].model.document
254
+ jsdoc.on_change(send_change.bind(null, jsdoc), false)
255
+ pyodideWorker.postMessage({'type': 'rendered'})
256
+ pyodideWorker.postMessage({'type': 'location', location: JSON.stringify(window.location)})
257
+ } else if (msg.type === 'patch') {
258
+ pyodideWorker.jsdoc.apply_json_patch(msg.patch, msg.buffers, setter_id='py')
259
+ }
260
+ };
261
+ </script>
262
+ <script type="application/json" id="p1167">
263
+ {"3c834f99-af01-4f86-b149-44983f2412fc":{"version":"3.3.2","title":"VideoStream with ScikitImage","roots":[{"type":"object","name":"panel.models.location.Location","id":"p1002","attributes":{"name":"location","reload":false}},{"type":"object","name":"panel.models.markup.HTML","id":"p1009","attributes":{"name":"js_area","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1079","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1076","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/bundled/theme/material.css"}}],"width":0,"height":0,"margin":0,"sizing_mode":"fixed","align":"start","disable_math":true}},{"type":"object","name":"panel.models.reactive_html.ReactiveHTML","id":"p1012","attributes":{"name":"actions","subscribed_events":{"type":"set","entries":["dom_event"]},"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1080","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":0,"align":"start","data":{"type":"object","name":"MaterialTemplateActions1","id":"p1010","attributes":{"name":"MaterialTemplateActions00194"}},"scripts":{"type":"map","entries":[["open_modal",["modal.open();\nsetTimeout(function() {{\n window.dispatchEvent(new Event(&amp;#x27;resize&amp;#x27;));\n}}, 200);"]],["close_modal",["modal.close()"]]]}}},{"type":"object","name":"panel.models.browser.BrowserInfo","id":"p1013","attributes":{"name":"browser_info"}},{"type":"object","name":"panel.models.markup.HTML","id":"p1016","attributes":{"name":"busy_indicator","css_classes":["loader","light"],"stylesheets":[":host { --loading-spinner-size: 20px; }","\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1081","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1082","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loadingspinner.css"}},{"id":"p1076"}],"min_width":20,"min_height":20,"margin":[5,10],"align":"start"}},{"type":"object","name":"Row","id":"p1017","attributes":{"name":"main-139994947403408","tags":["main"],"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1083","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1084","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"id":"p1076"}],"min_width":600,"margin":0,"sizing_mode":"stretch_width","align":"center","children":[{"type":"object","name":"panel.models.widgets.VideoStream","id":"p1019","attributes":{"visible":false,"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1085","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"width":0,"height":0,"margin":[5,10],"sizing_mode":"fixed","align":"start","format":"jpeg","timeout":500,"value":""}},{"type":"object","name":"Spacer","id":"p1021","attributes":{"name":"HSpacer00182","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1086","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":0,"sizing_mode":"stretch_width","align":"start"}},{"type":"object","name":"panel.models.markup.HTML","id":"p1023","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1087","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"width":600,"height":600,"margin":[5,10],"sizing_mode":"fixed","align":"start","text":"&lt;img&gt;&lt;/img&gt;"}},{"type":"object","name":"Spacer","id":"p1025","attributes":{"name":"HSpacer00183","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1088","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":0,"sizing_mode":"stretch_width","align":"start"}}]}},{"type":"object","name":"panel.models.layout.Column","id":"p1028","attributes":{"name":"nav-139994947264208","tags":["nav"],"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1089","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1090","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"id":"p1076"}],"margin":0,"sizing_mode":"stretch_width","align":"start","children":[{"type":"object","name":"panel.models.layout.Column","id":"p1029","attributes":{"name":"Video Stream","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1137","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1138","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","children":[{"type":"object","name":"Div","id":"p1031","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1094","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","text":"&lt;b&gt;Video Stream&lt;/b&gt;"}},{"type":"object","name":"Checkbox","id":"p1033","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1095","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","label":"Paused"}},{"type":"object","name":"Slider","id":"p1035","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1096","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","title":"Timeout","start":10,"end":2000,"value":500,"step":10}}]}},{"type":"object","name":"panel.models.layout.Column","id":"p1038","attributes":{"name":"Column00175","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1143","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1144","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"id":"p1076"}],"margin":0,"sizing_mode":"stretch_width","align":"start","children":[{"type":"object","name":"Row","id":"p1039","attributes":{"name":"Row00148","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1100","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1101","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"id":"p1076"}],"margin":0,"sizing_mode":"stretch_width","align":"start"}}]}},{"type":"object","name":"panel.models.layout.Column","id":"p1044","attributes":{"name":"Image","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1148","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1149","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","children":[{"type":"object","name":"Div","id":"p1046","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1105","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","text":"&lt;b&gt;Image&lt;/b&gt;"}},{"type":"object","name":"Slider","id":"p1048","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1106","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","title":"Height","start":10,"end":2000,"value":600,"step":10}},{"type":"object","name":"Slider","id":"p1050","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1107","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","title":"Width","start":10,"end":2000,"value":600,"step":10}}]}},{"type":"object","name":"panel.models.layout.Column","id":"p1053","attributes":{"name":"Model","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1154","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1155","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","children":[{"type":"object","name":"Div","id":"p1055","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1111","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","text":"&lt;b&gt;Model&lt;/b&gt;"}},{"type":"object","name":"panel.models.widgets.RadioButtonGroup","id":"p1059","attributes":{"button_type":"primary","css_classes":["outline"],"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1112","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1113","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/button.css"}},{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","labels":["GaussianBlur","Grayscale","Sobel","FaceDetection"],"orientation":"vertical","active":0,"tooltip":{"type":"object","name":"Tooltip","id":"p1056","attributes":{"position":"right","content":"The currently selected model"}}}}]}},{"type":"object","name":"panel.models.layout.Column","id":"p1062","attributes":{"name":"Column00180","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1160","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1161","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"id":"p1076"}],"margin":0,"sizing_mode":"stretch_width","align":"start","children":[{"type":"object","name":"panel.models.layout.Column","id":"p1063","attributes":{"name":"GaussianBlur","stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1163","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"type":"object","name":"ImportedStyleSheet","id":"p1164","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/listpanel.css"}},{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","children":[{"type":"object","name":"Div","id":"p1065","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1120","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","text":"&lt;b&gt;GaussianBlur&lt;/b&gt;"}},{"type":"object","name":"Slider","id":"p1067","attributes":{"stylesheets":["\n:host(.pn-loading.pn-arc):before, .pn-loading.pn-arc:before {\n background-image: url(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJtYXJnaW46IGF1dG87IGJhY2tncm91bmQ6IG5vbmU7IGRpc3BsYXk6IGJsb2NrOyBzaGFwZS1yZW5kZXJpbmc6IGF1dG87IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjYzNjM2MzIiBzdHJva2Utd2lkdGg9IjEwIiByPSIzNSIgc3Ryb2tlLWRhc2hhcnJheT0iMTY0LjkzMzYxNDMxMzQ2NDE1IDU2Ljk3Nzg3MTQzNzgyMTM4Ij4gICAgPGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIiBkdXI9IjFzIiB2YWx1ZXM9IjAgNTAgNTA7MzYwIDUwIDUwIiBrZXlUaW1lcz0iMDsxIj48L2FuaW1hdGVUcmFuc2Zvcm0+ICA8L2NpcmNsZT48L3N2Zz4=\");\n background-size: auto calc(min(50%, 400px));\n}",{"type":"object","name":"ImportedStyleSheet","id":"p1121","attributes":{"url":"https://cdn.holoviz.org/panel/1.3.6/dist/css/loading.css"}},"\n.mdc-drawer {background: var(--light-bg-color) !important;}",{"id":"p1076"}],"margin":[5,10],"sizing_mode":"stretch_width","align":"start","title":"Radius","start":0,"end":10,"value":2}}]}}]}}]}}],"defs":[{"type":"model","name":"ReactiveHTML1"},{"type":"model","name":"FlexBox1","properties":[{"name":"align_content","kind":"Any","default":"flex-start"},{"name":"align_items","kind":"Any","default":"flex-start"},{"name":"flex_direction","kind":"Any","default":"row"},{"name":"flex_wrap","kind":"Any","default":"wrap"},{"name":"justify_content","kind":"Any","default":"flex-start"}]},{"type":"model","name":"FloatPanel1","properties":[{"name":"config","kind":"Any","default":{"type":"map"}},{"name":"contained","kind":"Any","default":true},{"name":"position","kind":"Any","default":"right-top"},{"name":"offsetx","kind":"Any","default":null},{"name":"offsety","kind":"Any","default":null},{"name":"theme","kind":"Any","default":"primary"},{"name":"status","kind":"Any","default":"normalized"}]},{"type":"model","name":"GridStack1","properties":[{"name":"mode","kind":"Any","default":"warn"},{"name":"ncols","kind":"Any","default":null},{"name":"nrows","kind":"Any","default":null},{"name":"allow_resize","kind":"Any","default":true},{"name":"allow_drag","kind":"Any","default":true},{"name":"state","kind":"Any","default":[]}]},{"type":"model","name":"drag1","properties":[{"name":"slider_width","kind":"Any","default":5},{"name":"slider_color","kind":"Any","default":"black"},{"name":"value","kind":"Any","default":50}]},{"type":"model","name":"click1","properties":[{"name":"terminal_output","kind":"Any","default":""},{"name":"debug_name","kind":"Any","default":""},{"name":"clears","kind":"Any","default":0}]},{"type":"model","name":"copy_to_clipboard1","properties":[{"name":"fill","kind":"Any","default":"none"},{"name":"value","kind":"Any","default":null}]},{"type":"model","name":"FastWrapper1","properties":[{"name":"object","kind":"Any","default":null},{"name":"style","kind":"Any","default":null}]},{"type":"model","name":"NotificationAreaBase1","properties":[{"name":"js_events","kind":"Any","default":{"type":"map"}},{"name":"position","kind":"Any","default":"bottom-right"},{"name":"_clear","kind":"Any","default":0}]},{"type":"model","name":"NotificationArea1","properties":[{"name":"js_events","kind":"Any","default":{"type":"map"}},{"name":"notifications","kind":"Any","default":[]},{"name":"position","kind":"Any","default":"bottom-right"},{"name":"_clear","kind":"Any","default":0},{"name":"types","kind":"Any","default":[{"type":"map","entries":[["type","warning"],["background","#ffc107"],["icon",{"type":"map","entries":[["className","fas fa-exclamation-triangle"],["tagName","i"],["color","white"]]}]]},{"type":"map","entries":[["type","info"],["background","#007bff"],["icon",{"type":"map","entries":[["className","fas fa-info-circle"],["tagName","i"],["color","white"]]}]]}]}]},{"type":"model","name":"Notification","properties":[{"name":"background","kind":"Any","default":null},{"name":"duration","kind":"Any","default":3000},{"name":"icon","kind":"Any","default":null},{"name":"message","kind":"Any","default":""},{"name":"notification_type","kind":"Any","default":null},{"name":"_destroyed","kind":"Any","default":false}]},{"type":"model","name":"TemplateActions1","properties":[{"name":"open_modal","kind":"Any","default":0},{"name":"close_modal","kind":"Any","default":0}]},{"type":"model","name":"BootstrapTemplateActions1","properties":[{"name":"open_modal","kind":"Any","default":0},{"name":"close_modal","kind":"Any","default":0}]},{"type":"model","name":"MaterialTemplateActions1","properties":[{"name":"open_modal","kind":"Any","default":0},{"name":"close_modal","kind":"Any","default":0}]}]}}
264
+ </script>
265
+ <script type="text/javascript">
266
+ (function() {
267
+ const fn = function() {
268
+ Bokeh.safely(function() {
269
+ (function(root) {
270
+ function embed_document(root) {
271
+ const docs_json = document.getElementById('p1167').textContent;
272
+ const render_items = [{"docid":"3c834f99-af01-4f86-b149-44983f2412fc","roots":{"p1002":"c861e5fb-b514-4e68-8b16-9174f1ba9a9c","p1009":"b6ab6366-6c22-4f7f-b812-f2e1a390e026","p1012":"bf2a4026-7554-4e87-bfde-2673dedfcd49","p1013":"ade4f51b-e294-42a9-a3e7-478d065f730b","p1016":"c489da48-6f7a-4f64-bb54-4971c884670a","p1017":"b7e25cf5-128e-499f-ac93-6fb5a5859569","p1028":"c5ca8963-30c7-446b-9af0-4a5fb9a34bfc"},"root_ids":["p1002","p1009","p1012","p1013","p1016","p1017","p1028"]}];
273
+ root.Bokeh.embed.embed_items(docs_json, render_items);
274
+ }
275
+ if (root.Bokeh !== undefined) {
276
+ embed_document(root);
277
+ } else {
278
+ let attempts = 0;
279
+ const timer = setInterval(function(root) {
280
+ if (root.Bokeh !== undefined) {
281
+ clearInterval(timer);
282
+ embed_document(root);
283
+ } else {
284
+ attempts++;
285
+ if (attempts > 100) {
286
+ clearInterval(timer);
287
+ console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
288
+ }
289
+ }
290
+ }, 10, root)
291
+ }
292
+ })(window);
293
+ });
294
+ };
295
+ if (document.readyState != "loading") fn();
296
+ else document.addEventListener("DOMContentLoaded", fn);
297
+ })();
298
+ </script>
299
+ </body>
300
+ </html>
index.js ADDED
@@ -0,0 +1,524 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ importScripts("https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js");
2
+
3
+ function sendPatch(patch, buffers, msg_id) {
4
+ self.postMessage({
5
+ type: 'patch',
6
+ patch: patch,
7
+ buffers: buffers
8
+ })
9
+ }
10
+
11
+ async function startApplication() {
12
+ console.log("Loading pyodide!");
13
+ self.postMessage({type: 'status', msg: 'Loading pyodide'})
14
+ self.pyodide = await loadPyodide();
15
+ self.pyodide.globals.set("sendPatch", sendPatch);
16
+ console.log("Loaded!");
17
+ await self.pyodide.loadPackage("micropip");
18
+ const env_spec = ['https://cdn.holoviz.org/panel/wheels/bokeh-3.3.2-py3-none-any.whl', 'https://cdn.holoviz.org/panel/1.3.6/dist/wheels/panel-1.3.6-py3-none-any.whl', 'pyodide-http==0.2.1', 'numpy', 'param', 'scikit-image']
19
+ for (const pkg of env_spec) {
20
+ let pkg_name;
21
+ if (pkg.endsWith('.whl')) {
22
+ pkg_name = pkg.split('/').slice(-1)[0].split('-')[0]
23
+ } else {
24
+ pkg_name = pkg
25
+ }
26
+ self.postMessage({type: 'status', msg: `Installing ${pkg_name}`})
27
+ try {
28
+ await self.pyodide.runPythonAsync(`
29
+ import micropip
30
+ await micropip.install('${pkg}');
31
+ `);
32
+ } catch(e) {
33
+ console.log(e)
34
+ self.postMessage({
35
+ type: 'status',
36
+ msg: `Error while installing ${pkg_name}`
37
+ });
38
+ }
39
+ }
40
+ console.log("Packages loaded!");
41
+ self.postMessage({type: 'status', msg: 'Executing code'})
42
+ const code = `
43
+
44
+ import asyncio
45
+
46
+ from panel.io.pyodide import init_doc, write_doc
47
+
48
+ init_doc()
49
+
50
+ import base64
51
+ import io
52
+ import time
53
+
54
+ import numpy as np
55
+ import param
56
+ import PIL
57
+ import skimage
58
+
59
+ from PIL import Image, ImageFilter
60
+ from skimage import data, filters
61
+ from skimage.color.adapt_rgb import adapt_rgb, each_channel
62
+ from skimage.draw import rectangle
63
+ from skimage.exposure import rescale_intensity
64
+ from skimage.feature import Cascade
65
+
66
+ import panel as pn
67
+ import sys
68
+
69
+
70
+
71
+ HEIGHT = 600 # pixels
72
+ WIDTH = 600 # pixels
73
+ TIMEOUT = 500 # milliseconds
74
+
75
+ if sys.platform == 'emscripten':
76
+ HEIGHT = 800 # pixels
77
+ WIDTH = 800 # pixels
78
+ TIMEOUT=100
79
+
80
+ CSS="""
81
+ .mdc-drawer {background: var(--light-bg-color) !important;}"""
82
+
83
+ pn.extension(raw_css=[CSS], sizing_mode="stretch_width")
84
+
85
+
86
+ class ImageModel(pn.viewable.Viewer):
87
+ """Base class for image models."""
88
+
89
+ def __init__(self, **params):
90
+ super().__init__(**params)
91
+
92
+ with param.edit_constant(self):
93
+ self.name = self.__class__.name.replace("Model", "")
94
+ self.view = self.create_view()
95
+
96
+ def __panel__(self):
97
+ return self.view
98
+
99
+ def apply(self, image: str, height: int = HEIGHT, width: int = WIDTH) -> str:
100
+ """Transforms a base64 encoded jpg image to a base64 encoded jpg BytesIO object"""
101
+ raise NotImplementedError()
102
+
103
+ def create_view(self):
104
+ """Creates a view of the parameters of the transform to enable the user to configure them"""
105
+ return pn.Param(self, name=self.name)
106
+
107
+ def transform(self, image):
108
+ """Transforms the image"""
109
+ raise NotImplementedError()
110
+
111
+
112
+ class PILImageModel(ImageModel):
113
+ """Base class for PIL image models"""
114
+
115
+ @staticmethod
116
+ def to_pil_img(value: str, height=HEIGHT, width=WIDTH):
117
+ """Converts a base64 jpeg image string to a PIL.Image"""
118
+ encoded_data = value.split(",")[1]
119
+ base64_decoded = base64.b64decode(encoded_data)
120
+ image = Image.open(io.BytesIO(base64_decoded))
121
+ image.draft("RGB", (height, width))
122
+ return image
123
+
124
+ @staticmethod
125
+ def from_pil_img(image: Image):
126
+ """Converts a PIL.Image to a base64 encoded JPG BytesIO object"""
127
+ buff = io.BytesIO()
128
+ image.save(buff, format="JPEG")
129
+ return buff
130
+
131
+ def apply(self, image: str, height: int = HEIGHT, width: int = WIDTH) -> io.BytesIO:
132
+ pil_img = self.to_pil_img(image, height=height, width=width)
133
+
134
+ transformed_image = self.transform(pil_img)
135
+
136
+ return self.from_pil_img(transformed_image)
137
+
138
+ def transform(self, image: PIL.Image) -> PIL.Image:
139
+ """Transforms the PIL.Image image"""
140
+ raise NotImplementedError()
141
+
142
+
143
+ class NumpyImageModel(ImageModel):
144
+ """Base class for np.ndarray image models"""
145
+
146
+ @staticmethod
147
+ def to_np_ndarray(image: str, height=HEIGHT, width=WIDTH) -> np.ndarray:
148
+ """Converts a base64 encoded jpeg string to a np.ndarray"""
149
+ pil_img = PILImageModel.to_pil_img(image, height=height, width=width)
150
+ return np.array(pil_img)
151
+
152
+ @staticmethod
153
+ def from_np_ndarray(image: np.ndarray) -> io.BytesIO:
154
+ """Converts np.ndarray jpeg image to a jpeg BytesIO instance"""
155
+ if image.dtype == np.dtype("float64"):
156
+ image = (image * 255).astype(np.uint8)
157
+ pil_img = PIL.Image.fromarray(image)
158
+ return PILImageModel.from_pil_img(pil_img)
159
+
160
+ def apply(self, image: str, height: int = HEIGHT, width: int = WIDTH) -> io.BytesIO:
161
+ np_array = self.to_np_ndarray(image, height=height, width=width)
162
+
163
+ transformed_image = self.transform(np_array)
164
+
165
+ return self.from_np_ndarray(transformed_image)
166
+
167
+ def transform(self, image: np.ndarray) -> np.ndarray:
168
+ """Transforms the np.array image"""
169
+ raise NotImplementedError()
170
+
171
+
172
+ class Timer(pn.viewable.Viewer):
173
+ """Helper Component used to show duration trends"""
174
+
175
+ _trends = param.Dict()
176
+
177
+ def __init__(self, **params):
178
+ super().__init__()
179
+
180
+ self.last_updates = {}
181
+ self._trends = {}
182
+
183
+ self._layout = pn.Row(**params)
184
+
185
+ def time_it(self, name, func, *args, **kwargs):
186
+ """Measures the duration of the execution of the func function and reports it under the
187
+ name specified"""
188
+ start = time.time()
189
+ result = func(*args, **kwargs)
190
+ end = time.time()
191
+ duration = round(end - start, 2)
192
+ self._report(name=name, duration=duration)
193
+ return result
194
+
195
+ def inc_it(self, name):
196
+ """Measures the duration since the last time inc_it was called and reports it under the
197
+ specified name"""
198
+ start = self.last_updates.get(name, time.time())
199
+ end = time.time()
200
+ duration = round(end - start, 2)
201
+ self._report(name=name, duration=duration)
202
+ self.last_updates[name] = end
203
+
204
+ def _report(self, name, duration):
205
+ if not name in self._trends:
206
+ self._trends[name] = pn.indicators.Trend(
207
+ name=name,
208
+ data={"x": [1], "y": [duration]},
209
+ height=100,
210
+ width=150,
211
+ sizing_mode="fixed",
212
+ )
213
+ self.param.trigger("_trends")
214
+ else:
215
+ trend = self._trends[name]
216
+ next_x = max(trend.data["x"]) + 1
217
+ trend.stream({"x": [next_x], "y": [duration]}, rollover=10)
218
+
219
+ @param.depends("_trends")
220
+ def _panel(self):
221
+ self._layout[:] = list(self._trends.values())
222
+ return self._layout
223
+
224
+ def __panel__(self):
225
+ return pn.panel(self._panel)
226
+
227
+
228
+ def to_instance(value, **params):
229
+ """Converts the value to an instance
230
+
231
+ Args:
232
+ value: A param.Parameterized class or instance
233
+
234
+ Returns:
235
+ An instance of the param.Parameterized class
236
+ """
237
+ if isinstance(value, param.Parameterized):
238
+ value.param.update(**params)
239
+ return value
240
+ return value(**params)
241
+
242
+
243
+ class VideoStreamInterface(pn.viewable.Viewer):
244
+ """An easy to use interface for a VideoStream and a set of transforms"""
245
+
246
+ video_stream = param.ClassSelector(
247
+ class_=pn.widgets.VideoStream, constant=True, doc="The source VideoStream", allow_refs=False,
248
+ )
249
+
250
+ height = param.Integer(
251
+ default=HEIGHT,
252
+ bounds=(10, 2000),
253
+ step=10,
254
+ doc="""The height of the image converted and shown""",
255
+ )
256
+ width = param.Integer(
257
+ default=WIDTH,
258
+ bounds=(10, 2000),
259
+ step=10,
260
+ doc="""The width of the image converted and shown""",
261
+ )
262
+
263
+ model = param.Selector(doc="The currently selected model")
264
+
265
+ def __init__(
266
+ self,
267
+ models,
268
+ timeout=TIMEOUT,
269
+ paused=False,
270
+ **params,
271
+ ):
272
+ super().__init__(
273
+ video_stream=pn.widgets.VideoStream(
274
+ name="Video Stream",
275
+ timeout=timeout,
276
+ paused=paused,
277
+ height=0,
278
+ width=0,
279
+ visible=False,
280
+ format="jpeg",
281
+ ),
282
+ **params,
283
+ )
284
+ self.image = pn.pane.JPG(
285
+ height=self.height, width=self.width, sizing_mode="fixed"
286
+ )
287
+ self._updating = False
288
+ models = [to_instance(model) for model in models]
289
+ self.param.model.objects = models
290
+ self.model = models[0]
291
+ self.timer = Timer(sizing_mode="stretch_width")
292
+ self.settings = self._create_settings()
293
+ self._panel = self._create_panel()
294
+
295
+ def _create_settings(self):
296
+ return pn.Column(
297
+ pn.Param(
298
+ self.video_stream,
299
+ parameters=["timeout", "paused"],
300
+ widgets={
301
+ "timeout": {
302
+ "widget_type": pn.widgets.IntSlider,
303
+ "start": 10,
304
+ "end": 2000,
305
+ "step": 10,
306
+ }
307
+ },
308
+ ),
309
+ self.timer,
310
+ pn.Param(self, parameters=["height", "width"], name="Image"),
311
+ pn.Param(
312
+ self,
313
+ parameters=["model"],
314
+ expand_button=False,
315
+ expand=False,
316
+ widgets={
317
+ "model": {
318
+ "widget_type": pn.widgets.RadioButtonGroup,
319
+ "orientation": "vertical",
320
+ "button_type": "primary",
321
+ "button_style": "outline"
322
+ }
323
+ },
324
+ name="Model",
325
+ ),
326
+ self._get_transform,
327
+ )
328
+
329
+ def _create_panel(self):
330
+ return pn.Row(
331
+ self.video_stream,
332
+ pn.layout.HSpacer(),
333
+ self.image,
334
+ pn.layout.HSpacer(),
335
+ sizing_mode="stretch_width",
336
+ align="center",
337
+ )
338
+
339
+ @param.depends("height", "width", watch=True)
340
+ def _update_height_width(self):
341
+ self.image.height = self.height
342
+ self.image.width = self.width
343
+
344
+ @param.depends("model")
345
+ def _get_transform(self):
346
+ # Hack: returning self.transform stops working after browsing the transforms for a while
347
+ return self.model.view
348
+
349
+ def __panel__(self):
350
+ return self._panel
351
+
352
+ @param.depends("video_stream.value", watch=True)
353
+ def _handle_stream(self):
354
+ if self._updating:
355
+ return
356
+
357
+ self._updating = True
358
+ if self.model and self.video_stream.value:
359
+ value = self.video_stream.value
360
+ try:
361
+ image = self.timer.time_it(
362
+ name="Model",
363
+ func=self.model.apply,
364
+ image=value,
365
+ height=self.height,
366
+ width=self.width,
367
+ )
368
+ self.image.object = image
369
+ except PIL.UnidentifiedImageError:
370
+ print("unidentified image")
371
+
372
+ self.timer.inc_it("Last Update")
373
+ self._updating = False
374
+
375
+
376
+ class GaussianBlurModel(PILImageModel):
377
+ """Gaussian Blur Model
378
+
379
+ https://pillow.readthedocs.io/en/stable/reference/ImageFilter.html#PIL.ImageFilter.GaussianBlur
380
+ """
381
+
382
+ radius = param.Integer(default=0, bounds=(0, 10))
383
+
384
+ def transform(self, image: Image):
385
+ return image.filter(ImageFilter.GaussianBlur(radius=self.radius))
386
+
387
+
388
+ class GrayscaleModel(NumpyImageModel):
389
+ """GrayScale Model
390
+
391
+ https://scikit-image.org/docs/0.15.x/auto_examples/color_exposure/plot_rgb_to_gray.html
392
+ """
393
+
394
+ def transform(self, image: np.ndarray):
395
+ grayscale = skimage.color.rgb2gray(image[:, :, :3])
396
+ return skimage.color.gray2rgb(grayscale)
397
+
398
+
399
+ class SobelModel(NumpyImageModel):
400
+ """Sobel Model
401
+
402
+ https://scikit-image.org/docs/0.15.x/auto_examples/color_exposure/plot_adapt_rgb.html
403
+ """
404
+ def transform(self, image):
405
+
406
+
407
+ @adapt_rgb(each_channel)
408
+ def sobel_each(image):
409
+ return filters.sobel(image)
410
+
411
+ return rescale_intensity(1 - sobel_each(image))
412
+
413
+
414
+ @pn.cache()
415
+ def get_detector():
416
+ """Returns the Cascade detector"""
417
+ trained_file = data.lbp_frontal_face_cascade_filename()
418
+ return Cascade(trained_file)
419
+
420
+
421
+ class FaceDetectionModel(NumpyImageModel):
422
+ """Face detection using a cascade classifier.
423
+
424
+ https://scikit-image.org/docs/0.15.x/auto_examples/applications/plot_face_detection.html
425
+ """
426
+
427
+ scale_factor = param.Number(default=1.4, bounds=(1.0, 2.0), step=0.1)
428
+ step_ratio = param.Integer(default=1, bounds=(1, 10))
429
+ size_x = param.Range(default=(60, 322), bounds=(10, 500))
430
+ size_y = param.Range(default=(60, 322), bounds=(10, 500))
431
+
432
+ def transform(self, image):
433
+ detector = get_detector()
434
+ detected = detector.detect_multi_scale(
435
+ img=image,
436
+ scale_factor=self.scale_factor,
437
+ step_ratio=self.step_ratio,
438
+ min_size=(self.size_x[0], self.size_y[0]),
439
+ max_size=(self.size_x[1], self.size_y[1]),
440
+ )
441
+
442
+ for patch in detected:
443
+ rrr, ccc = rectangle(
444
+ start=(patch["r"], patch["c"]),
445
+ extent=(patch["height"], patch["width"]),
446
+ shape=image.shape[:2],
447
+ )
448
+ image[rrr, ccc, 0] = 200
449
+
450
+ return image
451
+
452
+
453
+ component = VideoStreamInterface(
454
+ models=[
455
+ GaussianBlurModel,
456
+ GrayscaleModel,
457
+ SobelModel,
458
+ FaceDetectionModel,
459
+ ]
460
+ )
461
+ pn.Row(pn.Row(component.settings, max_width=400), component)
462
+
463
+
464
+ pn.template.MaterialTemplate(
465
+ site="Awesome Panel",
466
+ title="VideoStream with ScikitImage",
467
+ sidebar=[component.settings],
468
+ main=[component],
469
+ ).servable(); # We add ; to not show the template in the notebook as it does not display well.
470
+
471
+ await write_doc()
472
+ `
473
+
474
+ try {
475
+ const [docs_json, render_items, root_ids] = await self.pyodide.runPythonAsync(code)
476
+ self.postMessage({
477
+ type: 'render',
478
+ docs_json: docs_json,
479
+ render_items: render_items,
480
+ root_ids: root_ids
481
+ })
482
+ } catch(e) {
483
+ const traceback = `${e}`
484
+ const tblines = traceback.split('\n')
485
+ self.postMessage({
486
+ type: 'status',
487
+ msg: tblines[tblines.length-2]
488
+ });
489
+ throw e
490
+ }
491
+ }
492
+
493
+ self.onmessage = async (event) => {
494
+ const msg = event.data
495
+ if (msg.type === 'rendered') {
496
+ self.pyodide.runPythonAsync(`
497
+ from panel.io.state import state
498
+ from panel.io.pyodide import _link_docs_worker
499
+
500
+ _link_docs_worker(state.curdoc, sendPatch, setter='js')
501
+ `)
502
+ } else if (msg.type === 'patch') {
503
+ self.pyodide.globals.set('patch', msg.patch)
504
+ self.pyodide.runPythonAsync(`
505
+ state.curdoc.apply_json_patch(patch.to_py(), setter='js')
506
+ `)
507
+ self.postMessage({type: 'idle'})
508
+ } else if (msg.type === 'location') {
509
+ self.pyodide.globals.set('location', msg.location)
510
+ self.pyodide.runPythonAsync(`
511
+ import json
512
+ from panel.io.state import state
513
+ from panel.util import edit_readonly
514
+ if state.location:
515
+ loc_data = json.loads(location)
516
+ with edit_readonly(state.location):
517
+ state.location.param.update({
518
+ k: v for k, v in loc_data.items() if k in state.location.param
519
+ })
520
+ `)
521
+ }
522
+ }
523
+
524
+ startApplication()
index.py ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import io
3
+ import time
4
+
5
+ import numpy as np
6
+ import param
7
+ import PIL
8
+ import skimage
9
+
10
+ from PIL import Image, ImageFilter
11
+ from skimage import data, filters
12
+ from skimage.color.adapt_rgb import adapt_rgb, each_channel
13
+ from skimage.draw import rectangle
14
+ from skimage.exposure import rescale_intensity
15
+ from skimage.feature import Cascade
16
+
17
+ import panel as pn
18
+ import sys
19
+
20
+
21
+
22
+ HEIGHT = 600 # pixels
23
+ WIDTH = 600 # pixels
24
+ TIMEOUT = 500 # milliseconds
25
+
26
+ if sys.platform == 'emscripten':
27
+ # Performance is higher when no round trip to server
28
+ HEIGHT = 800
29
+ WIDTH = 800
30
+ TIMEOUT=100
31
+
32
+ CSS="""
33
+ .mdc-drawer {background: var(--light-bg-color) !important;}"""
34
+
35
+ pn.extension(raw_css=[CSS], sizing_mode="stretch_width")
36
+
37
+
38
+ class ImageModel(pn.viewable.Viewer):
39
+ """Base class for image models."""
40
+
41
+ def __init__(self, **params):
42
+ super().__init__(**params)
43
+
44
+ with param.edit_constant(self):
45
+ self.name = self.__class__.name.replace("Model", "")
46
+ self.view = self.create_view()
47
+
48
+ def __panel__(self):
49
+ return self.view
50
+
51
+ def apply(self, image: str, height: int = HEIGHT, width: int = WIDTH) -> str:
52
+ """Transforms a base64 encoded jpg image to a base64 encoded jpg BytesIO object"""
53
+ raise NotImplementedError()
54
+
55
+ def create_view(self):
56
+ """Creates a view of the parameters of the transform to enable the user to configure them"""
57
+ return pn.Param(self, name=self.name)
58
+
59
+ def transform(self, image):
60
+ """Transforms the image"""
61
+ raise NotImplementedError()
62
+
63
+
64
+ class PILImageModel(ImageModel):
65
+ """Base class for PIL image models"""
66
+
67
+ @staticmethod
68
+ def to_pil_img(value: str, height=HEIGHT, width=WIDTH):
69
+ """Converts a base64 jpeg image string to a PIL.Image"""
70
+ encoded_data = value.split(",")[1]
71
+ base64_decoded = base64.b64decode(encoded_data)
72
+ image = Image.open(io.BytesIO(base64_decoded))
73
+ image.draft("RGB", (height, width))
74
+ return image
75
+
76
+ @staticmethod
77
+ def from_pil_img(image: Image):
78
+ """Converts a PIL.Image to a base64 encoded JPG BytesIO object"""
79
+ buff = io.BytesIO()
80
+ image.save(buff, format="JPEG")
81
+ return buff
82
+
83
+ def apply(self, image: str, height: int = HEIGHT, width: int = WIDTH) -> io.BytesIO:
84
+ pil_img = self.to_pil_img(image, height=height, width=width)
85
+
86
+ transformed_image = self.transform(pil_img)
87
+
88
+ return self.from_pil_img(transformed_image)
89
+
90
+ def transform(self, image: PIL.Image) -> PIL.Image:
91
+ """Transforms the PIL.Image image"""
92
+ raise NotImplementedError()
93
+
94
+
95
+ class NumpyImageModel(ImageModel):
96
+ """Base class for np.ndarray image models"""
97
+
98
+ @staticmethod
99
+ def to_np_ndarray(image: str, height=HEIGHT, width=WIDTH) -> np.ndarray:
100
+ """Converts a base64 encoded jpeg string to a np.ndarray"""
101
+ pil_img = PILImageModel.to_pil_img(image, height=height, width=width)
102
+ return np.array(pil_img)
103
+
104
+ @staticmethod
105
+ def from_np_ndarray(image: np.ndarray) -> io.BytesIO:
106
+ """Converts np.ndarray jpeg image to a jpeg BytesIO instance"""
107
+ if image.dtype == np.dtype("float64"):
108
+ image = (image * 255).astype(np.uint8)
109
+ pil_img = PIL.Image.fromarray(image)
110
+ return PILImageModel.from_pil_img(pil_img)
111
+
112
+ def apply(self, image: str, height: int = HEIGHT, width: int = WIDTH) -> io.BytesIO:
113
+ np_array = self.to_np_ndarray(image, height=height, width=width)
114
+
115
+ transformed_image = self.transform(np_array)
116
+
117
+ return self.from_np_ndarray(transformed_image)
118
+
119
+ def transform(self, image: np.ndarray) -> np.ndarray:
120
+ """Transforms the np.array image"""
121
+ raise NotImplementedError()
122
+
123
+
124
+ class Timer(pn.viewable.Viewer):
125
+ """Helper Component used to show duration trends"""
126
+
127
+ _trends = param.Dict()
128
+
129
+ def __init__(self, **params):
130
+ super().__init__()
131
+
132
+ self.last_updates = {}
133
+ self._trends = {}
134
+
135
+ self._layout = pn.Row(**params)
136
+
137
+ def time_it(self, name, func, *args, **kwargs):
138
+ """Measures the duration of the execution of the func function and reports it under the
139
+ name specified"""
140
+ start = time.time()
141
+ result = func(*args, **kwargs)
142
+ end = time.time()
143
+ duration = round(end - start, 2)
144
+ self._report(name=name, duration=duration)
145
+ return result
146
+
147
+ def inc_it(self, name):
148
+ """Measures the duration since the last time inc_it was called and reports it under the
149
+ specified name"""
150
+ start = self.last_updates.get(name, time.time())
151
+ end = time.time()
152
+ duration = round(end - start, 2)
153
+ self._report(name=name, duration=duration)
154
+ self.last_updates[name] = end
155
+
156
+ def _report(self, name, duration):
157
+ if not name in self._trends:
158
+ self._trends[name] = pn.indicators.Trend(
159
+ name=name,
160
+ data={"x": [1], "y": [duration]},
161
+ height=100,
162
+ width=150,
163
+ sizing_mode="fixed",
164
+ )
165
+ self.param.trigger("_trends")
166
+ else:
167
+ trend = self._trends[name]
168
+ next_x = max(trend.data["x"]) + 1
169
+ trend.stream({"x": [next_x], "y": [duration]}, rollover=10)
170
+
171
+ @param.depends("_trends")
172
+ def _panel(self):
173
+ self._layout[:] = list(self._trends.values())
174
+ return self._layout
175
+
176
+ def __panel__(self):
177
+ return pn.panel(self._panel)
178
+
179
+
180
+ def to_instance(value, **params):
181
+ """Converts the value to an instance
182
+
183
+ Args:
184
+ value: A param.Parameterized class or instance
185
+
186
+ Returns:
187
+ An instance of the param.Parameterized class
188
+ """
189
+ if isinstance(value, param.Parameterized):
190
+ value.param.update(**params)
191
+ return value
192
+ return value(**params)
193
+
194
+
195
+ class VideoStreamInterface(pn.viewable.Viewer):
196
+ """An easy to use interface for a VideoStream and a set of transforms"""
197
+
198
+ video_stream = param.ClassSelector(
199
+ class_=pn.widgets.VideoStream, constant=True, doc="The source VideoStream", allow_refs=False,
200
+ )
201
+
202
+ height = param.Integer(
203
+ default=HEIGHT,
204
+ bounds=(10, 2000),
205
+ step=10,
206
+ doc="""The height of the image converted and shown""",
207
+ )
208
+ width = param.Integer(
209
+ default=WIDTH,
210
+ bounds=(10, 2000),
211
+ step=10,
212
+ doc="""The width of the image converted and shown""",
213
+ )
214
+
215
+ model = param.Selector(doc="The currently selected model")
216
+
217
+ def __init__(
218
+ self,
219
+ models,
220
+ timeout=TIMEOUT,
221
+ paused=False,
222
+ **params,
223
+ ):
224
+ super().__init__(
225
+ video_stream=pn.widgets.VideoStream(
226
+ name="Video Stream",
227
+ timeout=timeout,
228
+ paused=paused,
229
+ height=0,
230
+ width=0,
231
+ visible=False,
232
+ format="jpeg",
233
+ ),
234
+ **params,
235
+ )
236
+ self.image = pn.pane.JPG(
237
+ height=self.height, width=self.width, sizing_mode="fixed"
238
+ )
239
+ self._updating = False
240
+ models = [to_instance(model) for model in models]
241
+ self.param.model.objects = models
242
+ self.model = models[0]
243
+ self.timer = Timer(sizing_mode="stretch_width")
244
+ self.settings = self._create_settings()
245
+ self._panel = self._create_panel()
246
+
247
+ def _create_settings(self):
248
+ return pn.Column(
249
+ pn.Param(
250
+ self.video_stream,
251
+ parameters=["timeout", "paused"],
252
+ widgets={
253
+ "timeout": {
254
+ "widget_type": pn.widgets.IntSlider,
255
+ "start": 10,
256
+ "end": 2000,
257
+ "step": 10,
258
+ }
259
+ },
260
+ ),
261
+ self.timer,
262
+ pn.Param(self, parameters=["height", "width"], name="Image"),
263
+ pn.Param(
264
+ self,
265
+ parameters=["model"],
266
+ expand_button=False,
267
+ expand=False,
268
+ widgets={
269
+ "model": {
270
+ "widget_type": pn.widgets.RadioButtonGroup,
271
+ "orientation": "vertical",
272
+ "button_type": "primary",
273
+ "button_style": "outline"
274
+ }
275
+ },
276
+ name="Model",
277
+ ),
278
+ self._get_transform,
279
+ )
280
+
281
+ def _create_panel(self):
282
+ return pn.Row(
283
+ self.video_stream,
284
+ pn.layout.HSpacer(),
285
+ self.image,
286
+ pn.layout.HSpacer(),
287
+ sizing_mode="stretch_width",
288
+ align="center",
289
+ )
290
+
291
+ @param.depends("height", "width", watch=True)
292
+ def _update_height_width(self):
293
+ self.image.height = self.height
294
+ self.image.width = self.width
295
+
296
+ @param.depends("model")
297
+ def _get_transform(self):
298
+ # Hack: returning self.transform stops working after browsing the transforms for a while
299
+ return self.model.view
300
+
301
+ def __panel__(self):
302
+ return self._panel
303
+
304
+ @param.depends("video_stream.value", watch=True)
305
+ def _handle_stream(self):
306
+ if self._updating:
307
+ return
308
+
309
+ self._updating = True
310
+ if self.model and self.video_stream.value:
311
+ value = self.video_stream.value
312
+ try:
313
+ image = self.timer.time_it(
314
+ name="Model",
315
+ func=self.model.apply,
316
+ image=value,
317
+ height=self.height,
318
+ width=self.width,
319
+ )
320
+ self.image.object = image
321
+ except PIL.UnidentifiedImageError:
322
+ print("unidentified image")
323
+
324
+ self.timer.inc_it("Last Update")
325
+ self._updating = False
326
+
327
+
328
+ class GaussianBlurModel(PILImageModel):
329
+ """Gaussian Blur Model
330
+
331
+ https://pillow.readthedocs.io/en/stable/reference/ImageFilter.html#PIL.ImageFilter.GaussianBlur
332
+ """
333
+
334
+ radius = param.Integer(default=0, bounds=(0, 10))
335
+
336
+ def transform(self, image: Image):
337
+ return image.filter(ImageFilter.GaussianBlur(radius=self.radius))
338
+
339
+
340
+ class GrayscaleModel(NumpyImageModel):
341
+ """GrayScale Model
342
+
343
+ https://scikit-image.org/docs/0.15.x/auto_examples/color_exposure/plot_rgb_to_gray.html
344
+ """
345
+
346
+ def transform(self, image: np.ndarray):
347
+ grayscale = skimage.color.rgb2gray(image[:, :, :3])
348
+ return skimage.color.gray2rgb(grayscale)
349
+
350
+
351
+ class SobelModel(NumpyImageModel):
352
+ """Sobel Model
353
+
354
+ https://scikit-image.org/docs/0.15.x/auto_examples/color_exposure/plot_adapt_rgb.html
355
+ """
356
+ def transform(self, image):
357
+
358
+
359
+ @adapt_rgb(each_channel)
360
+ def sobel_each(image):
361
+ return filters.sobel(image)
362
+
363
+ return rescale_intensity(1 - sobel_each(image))
364
+
365
+
366
+ @pn.cache()
367
+ def get_detector():
368
+ """Returns the Cascade detector"""
369
+ trained_file = data.lbp_frontal_face_cascade_filename()
370
+ return Cascade(trained_file)
371
+
372
+
373
+ class FaceDetectionModel(NumpyImageModel):
374
+ """Face detection using a cascade classifier.
375
+
376
+ https://scikit-image.org/docs/0.15.x/auto_examples/applications/plot_face_detection.html
377
+ """
378
+
379
+ scale_factor = param.Number(default=1.4, bounds=(1.0, 2.0), step=0.1)
380
+ step_ratio = param.Integer(default=1, bounds=(1, 10))
381
+ size_x = param.Range(default=(60, 322), bounds=(10, 500))
382
+ size_y = param.Range(default=(60, 322), bounds=(10, 500))
383
+
384
+ def transform(self, image):
385
+ detector = get_detector()
386
+ detected = detector.detect_multi_scale(
387
+ img=image,
388
+ scale_factor=self.scale_factor,
389
+ step_ratio=self.step_ratio,
390
+ min_size=(self.size_x[0], self.size_y[0]),
391
+ max_size=(self.size_x[1], self.size_y[1]),
392
+ )
393
+
394
+ for patch in detected:
395
+ rrr, ccc = rectangle(
396
+ start=(patch["r"], patch["c"]),
397
+ extent=(patch["height"], patch["width"]),
398
+ shape=image.shape[:2],
399
+ )
400
+ image[rrr, ccc, 0] = 200
401
+
402
+ return image
403
+
404
+
405
+ component = VideoStreamInterface(
406
+ models=[
407
+ GaussianBlurModel,
408
+ GrayscaleModel,
409
+ SobelModel,
410
+ FaceDetectionModel,
411
+ ]
412
+ )
413
+ pn.Row(pn.Row(component.settings, max_width=400), component)
414
+
415
+
416
+ pn.template.MaterialTemplate(
417
+ site="Awesome Panel",
418
+ title="VideoStream with ScikitImage",
419
+ sidebar=[component.settings],
420
+ main=[component],
421
+ ).servable(); # We add ; to not show the template in the notebook as it does not display well.