holobench 1.25.2__py3-none-any.whl → 1.26.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. bencher/example/__init__.py +0 -0
  2. bencher/example/benchmark_data.py +196 -0
  3. bencher/example/example_all.py +45 -0
  4. bencher/example/example_categorical.py +99 -0
  5. bencher/example/example_composable_container.py +106 -0
  6. bencher/example/example_composable_container2.py +160 -0
  7. bencher/example/example_consts.py +39 -0
  8. bencher/example/example_custom_sweep.py +59 -0
  9. bencher/example/example_custom_sweep2.py +42 -0
  10. bencher/example/example_docs.py +34 -0
  11. bencher/example/example_filepath.py +27 -0
  12. bencher/example/example_float3D.py +101 -0
  13. bencher/example/example_float_cat.py +99 -0
  14. bencher/example/example_floats.py +89 -0
  15. bencher/example/example_floats2D.py +93 -0
  16. bencher/example/example_holosweep.py +98 -0
  17. bencher/example/example_holosweep_objects.py +111 -0
  18. bencher/example/example_holosweep_tap.py +144 -0
  19. bencher/example/example_image.py +155 -0
  20. bencher/example/example_levels.py +181 -0
  21. bencher/example/example_levels2.py +37 -0
  22. bencher/example/example_pareto.py +53 -0
  23. bencher/example/example_sample_cache.py +85 -0
  24. bencher/example/example_sample_cache_context.py +116 -0
  25. bencher/example/example_simple.py +134 -0
  26. bencher/example/example_simple_bool.py +35 -0
  27. bencher/example/example_simple_cat.py +48 -0
  28. bencher/example/example_simple_float.py +28 -0
  29. bencher/example/example_simple_float2d.py +29 -0
  30. bencher/example/example_strings.py +47 -0
  31. bencher/example/example_time_event.py +63 -0
  32. bencher/example/example_video.py +118 -0
  33. bencher/example/example_workflow.py +189 -0
  34. bencher/example/experimental/example_bokeh_plotly.py +38 -0
  35. bencher/example/experimental/example_hover_ex.py +45 -0
  36. bencher/example/experimental/example_hvplot_explorer.py +39 -0
  37. bencher/example/experimental/example_interactive.py +75 -0
  38. bencher/example/experimental/example_streamnd.py +49 -0
  39. bencher/example/experimental/example_streams.py +36 -0
  40. bencher/example/experimental/example_template.py +40 -0
  41. bencher/example/experimental/example_updates.py +84 -0
  42. bencher/example/experimental/example_vector.py +84 -0
  43. bencher/example/meta/example_meta.py +171 -0
  44. bencher/example/meta/example_meta_cat.py +25 -0
  45. bencher/example/meta/example_meta_float.py +23 -0
  46. bencher/example/meta/example_meta_levels.py +26 -0
  47. bencher/example/optuna/example_optuna.py +78 -0
  48. bencher/example/shelved/example_float2D_scatter.py +109 -0
  49. bencher/example/shelved/example_float3D_cone.py +96 -0
  50. bencher/example/shelved/example_kwargs.py +63 -0
  51. bencher/plotting/__init__.py +0 -0
  52. bencher/plotting/plot_filter.py +110 -0
  53. bencher/plotting/plt_cnt_cfg.py +75 -0
  54. bencher/results/__init__.py +0 -0
  55. bencher/results/bench_result.py +94 -0
  56. bencher/results/bench_result_base.py +476 -0
  57. bencher/results/composable_container/__init__.py +0 -0
  58. bencher/results/composable_container/composable_container_base.py +73 -0
  59. bencher/results/composable_container/composable_container_panel.py +39 -0
  60. bencher/results/composable_container/composable_container_video.py +184 -0
  61. bencher/results/float_formatter.py +44 -0
  62. bencher/results/holoview_result.py +753 -0
  63. bencher/results/optuna_result.py +354 -0
  64. bencher/results/panel_result.py +41 -0
  65. bencher/results/plotly_result.py +65 -0
  66. bencher/results/video_result.py +38 -0
  67. bencher/results/video_summary.py +222 -0
  68. bencher/variables/__init__.py +0 -0
  69. bencher/variables/inputs.py +202 -0
  70. bencher/variables/parametrised_sweep.py +208 -0
  71. bencher/variables/results.py +214 -0
  72. bencher/variables/sweep_base.py +162 -0
  73. bencher/variables/time.py +92 -0
  74. holobench-1.26.3.data/data/share/ament_index/resource_index/packages/bencher +0 -0
  75. holobench-1.26.3.data/data/share/bencher/package.xml +33 -0
  76. {holobench-1.25.2.dist-info → holobench-1.26.3.dist-info}/METADATA +5 -5
  77. holobench-1.26.3.dist-info/RECORD +93 -0
  78. holobench-1.25.2.dist-info/RECORD +0 -18
  79. {holobench-1.25.2.dist-info → holobench-1.26.3.dist-info}/LICENSE +0 -0
  80. {holobench-1.25.2.dist-info → holobench-1.26.3.dist-info}/WHEEL +0 -0
  81. {holobench-1.25.2.dist-info → holobench-1.26.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,354 @@
1
+ from __future__ import annotations
2
+ from typing import List
3
+ from copy import deepcopy
4
+
5
+ import numpy as np
6
+ import optuna
7
+ import panel as pn
8
+ from collections import defaultdict
9
+ from textwrap import wrap
10
+
11
+ import pandas as pd
12
+ import xarray as xr
13
+
14
+
15
+ from optuna.visualization import (
16
+ plot_param_importances,
17
+ plot_pareto_front,
18
+ )
19
+ from bencher.utils import hmap_canonical_input
20
+ from bencher.variables.time import TimeSnapshot, TimeEvent
21
+ from bencher.bench_cfg import BenchCfg
22
+ from bencher.plotting.plt_cnt_cfg import PltCntCfg
23
+
24
+
25
+ # from bencher.results.bench_result_base import BenchResultBase
26
+ from bencher.optuna_conversions import (
27
+ sweep_var_to_optuna_dist,
28
+ summarise_trial,
29
+ param_importance,
30
+ optuna_grid_search,
31
+ summarise_optuna_study,
32
+ sweep_var_to_suggest,
33
+ )
34
+
35
+
36
+ def convert_dataset_bool_dims_to_str(dataset: xr.Dataset) -> xr.Dataset:
37
+ """Given a dataarray that contains boolean coordinates, conver them to strings so that holoviews loads the data properly
38
+
39
+ Args:
40
+ dataarray (xr.DataArray): dataarray with boolean coordinates
41
+
42
+ Returns:
43
+ xr.DataArray: dataarray with boolean coordinates converted to strings
44
+ """
45
+ bool_coords = {}
46
+ for c in dataset.coords:
47
+ if dataset.coords[c].dtype == bool:
48
+ bool_coords[c] = [str(vals) for vals in dataset.coords[c].values]
49
+
50
+ if len(bool_coords) > 0:
51
+ return dataset.assign_coords(bool_coords)
52
+ return dataset
53
+
54
+
55
+ class OptunaResult:
56
+ def __init__(self, bench_cfg: BenchCfg) -> None:
57
+ self.bench_cfg = bench_cfg
58
+ # self.wrap_long_time_labels(bench_cfg) # todo remove
59
+ self.ds = xr.Dataset()
60
+ self.object_index = []
61
+ self.hmaps = defaultdict(dict)
62
+ self.result_hmaps = bench_cfg.result_hmaps
63
+ self.studies = []
64
+ self.plt_cnt_cfg = PltCntCfg()
65
+ self.plot_inputs = []
66
+
67
+ # self.width=600/
68
+ # self.height=600
69
+
70
+ # bench_res.objects.append(rv)
71
+ # bench_res.reference_index = len(bench_res.objects)
72
+
73
+ def post_setup(self):
74
+ self.plt_cnt_cfg = PltCntCfg.generate_plt_cnt_cfg(self.bench_cfg)
75
+ self.bench_cfg = self.wrap_long_time_labels(self.bench_cfg)
76
+ self.ds = convert_dataset_bool_dims_to_str(self.ds)
77
+
78
+ def to_xarray(self) -> xr.Dataset:
79
+ return self.ds
80
+
81
+ def setup_object_index(self):
82
+ self.object_index = []
83
+
84
+ def to_pandas(self, reset_index=True) -> pd.DataFrame:
85
+ """Get the xarray results as a pandas dataframe
86
+
87
+ Returns:
88
+ pd.DataFrame: The xarray results array as a pandas dataframe
89
+ """
90
+ ds = self.to_xarray().to_dataframe()
91
+ if reset_index:
92
+ return ds.reset_index()
93
+ return ds
94
+
95
+ def wrap_long_time_labels(self, bench_cfg):
96
+ """Takes a benchCfg and wraps any index labels that are too long to be plotted easily
97
+
98
+ Args:
99
+ bench_cfg (BenchCfg):
100
+
101
+ Returns:
102
+ BenchCfg: updated config with wrapped labels
103
+ """
104
+ if bench_cfg.over_time:
105
+ if self.ds.coords["over_time"].dtype == np.datetime64:
106
+ # plotly catastrophically fails to plot anything with the default long string representation of time, so convert to a shorter time representation
107
+ self.ds.coords["over_time"] = [
108
+ pd.to_datetime(t).strftime("%d-%m-%y %H-%M-%S")
109
+ for t in self.ds.coords["over_time"].values
110
+ ]
111
+ # wrap very long time event labels because otherwise the graphs are unreadable
112
+ if bench_cfg.time_event is not None:
113
+ self.ds.coords["over_time"] = [
114
+ "\n".join(wrap(t, 20)) for t in self.ds.coords["over_time"].values
115
+ ]
116
+ return bench_cfg
117
+
118
+ def to_optuna_plots(self) -> List[pn.pane.panel]:
119
+ """Create an optuna summary from the benchmark results
120
+
121
+ Returns:
122
+ List[pn.pane.panel]: A list of optuna plot summarising the benchmark process
123
+ """
124
+
125
+ return self.collect_optuna_plots()
126
+
127
+ def to_optuna_from_sweep(self, bench, n_trials=30):
128
+ optu = self.to_optuna_from_results(
129
+ bench.worker, n_trials=n_trials, extra_results=bench.results
130
+ )
131
+ return summarise_optuna_study(optu)
132
+
133
+ def to_optuna_from_results(
134
+ self,
135
+ worker,
136
+ n_trials=100,
137
+ extra_results: List[OptunaResult] = None,
138
+ sampler=optuna.samplers.TPESampler(),
139
+ ):
140
+ directions = []
141
+ for rv in self.bench_cfg.optuna_targets(True):
142
+ directions.append(rv.direction)
143
+
144
+ study = optuna.create_study(
145
+ sampler=sampler, directions=directions, study_name=self.bench_cfg.title
146
+ )
147
+
148
+ # add already calculated results
149
+ results_list = extra_results if extra_results is not None else [self]
150
+ for res in results_list:
151
+ if len(res.ds.sizes) > 0:
152
+ study.add_trials(res.bench_results_to_optuna_trials(True))
153
+
154
+ def wrapped(trial) -> tuple:
155
+ kwargs = {}
156
+ for iv in self.bench_cfg.input_vars:
157
+ kwargs[iv.name] = sweep_var_to_suggest(iv, trial)
158
+ result = worker(**kwargs)
159
+ output = []
160
+ for rv in self.bench_cfg.result_vars:
161
+ output.append(result[rv.name])
162
+ return tuple(output)
163
+
164
+ study.optimize(wrapped, n_trials=n_trials)
165
+ return study
166
+
167
+ def bench_results_to_optuna_trials(self, include_meta: bool = True) -> optuna.Study:
168
+ """Convert an xarray dataset to an optuna study so optuna can further optimise or plot the statespace
169
+
170
+ Args:
171
+ bench_cfg (BenchCfg): benchmark config to convert
172
+
173
+ Returns:
174
+ optuna.Study: optuna description of the study
175
+ """
176
+ if include_meta:
177
+ df = self.to_pandas()
178
+ all_vars = []
179
+ for v in self.bench_cfg.all_vars:
180
+ if type(v) != TimeEvent:
181
+ all_vars.append(v)
182
+
183
+ print("All vars", all_vars)
184
+ else:
185
+ all_vars = self.bench_cfg.input_vars
186
+ # df = self.ds.
187
+ # if "repeat" in self.
188
+ # if self.bench_cfg.repeats>1:
189
+ # df = self.bench_cfg.ds.mean("repeat").to_dataframe().reset_index()
190
+ # else:
191
+ df = self.to_pandas().reset_index()
192
+ # df = self.bench_cfg.ds.mean("repeat").to_dataframe.reset_index()
193
+ # self.bench_cfg.all_vars
194
+ # del self.bench_cfg.meta_vars[1]
195
+
196
+ trials = []
197
+ distributions = {}
198
+ for i in all_vars:
199
+ distributions[i.name] = sweep_var_to_optuna_dist(i)
200
+
201
+ for row in df.iterrows():
202
+ params = {}
203
+ values = []
204
+ for i in all_vars:
205
+ if type(i) == TimeSnapshot:
206
+ if type(row[1][i.name]) == np.datetime64:
207
+ params[i.name] = row[1][i.name].timestamp()
208
+ else:
209
+ params[i.name] = row[1][i.name]
210
+
211
+ for r in self.bench_cfg.optuna_targets():
212
+ values.append(row[1][r])
213
+
214
+ trials.append(
215
+ optuna.trial.create_trial(
216
+ params=params,
217
+ distributions=distributions,
218
+ values=values,
219
+ )
220
+ )
221
+ return trials
222
+
223
+ def bench_result_to_study(self, include_meta: bool) -> optuna.Study:
224
+ trials = self.bench_results_to_optuna_trials(include_meta)
225
+ study = optuna_grid_search(self.bench_cfg)
226
+ optuna.logging.set_verbosity(optuna.logging.CRITICAL)
227
+ import warnings
228
+
229
+ # /usr/local/lib/python3.10/dist-packages/optuna/samplers/_grid.py:224: UserWarning: over_time contains a value with the type of <class 'pandas._libs.tslibs.timestamps.Timestamp'>, which is not supported by `GridSampler`. Please make sure a value is `str`, `int`, `float`, `bool` or `None` for persistent storage.
230
+
231
+ # this is not disabling the warning
232
+ warnings.filterwarnings(action="ignore", category=UserWarning)
233
+ # remove optuna gridsearch warning as we are not using their gridsearch because it has the most inexplicably terrible performance I have ever seen in my life. How can a for loop of 400 iterations start out with 100ms per loop and increase to greater than a 1000ms after 250ish iterations!?!?!??!!??!
234
+ study.add_trials(trials)
235
+ return study
236
+
237
+ def get_best_trial_params(self, canonical=False):
238
+ studies = self.bench_result_to_study(True)
239
+ out = studies.best_trials[0].params
240
+ if canonical:
241
+ return hmap_canonical_input(out)
242
+ return out
243
+
244
+ def get_pareto_front_params(self):
245
+ return [p.params for p in self.studies[0].trials]
246
+
247
+ def collect_optuna_plots(self) -> List[pn.pane.panel]:
248
+ """Use optuna to plot various summaries of the optimisation
249
+
250
+ Args:
251
+ study (optuna.Study): The study to plot
252
+ bench_cfg (BenchCfg): Benchmark config with options used to generate the study
253
+
254
+ Returns:
255
+ List[pn.pane.Pane]: A list of plots
256
+ """
257
+
258
+ self.studies = [self.bench_result_to_study(True)]
259
+ titles = ["# Analysis"]
260
+ if self.bench_cfg.repeats > 1:
261
+ self.studies.append(self.bench_result_to_study(False))
262
+ titles = [
263
+ "# Parameter Importance With Repeats",
264
+ "# Parameter Importance Without Repeats",
265
+ ]
266
+
267
+ study_repeats_pane = pn.Row()
268
+ for study, title in zip(self.studies, titles):
269
+ study_pane = pn.Column()
270
+ target_names = self.bench_cfg.optuna_targets()
271
+ param_str = []
272
+
273
+ study_pane.append(pn.pane.Markdown(title))
274
+
275
+ if len(target_names) > 1:
276
+ if len(target_names) <= 3:
277
+ study_pane.append(
278
+ plot_pareto_front(
279
+ study, target_names=target_names, include_dominated_trials=False
280
+ )
281
+ )
282
+ else:
283
+ print("plotting pareto front of first 3 result variables")
284
+ study_pane.append(
285
+ plot_pareto_front(
286
+ study,
287
+ targets=lambda t: (t.values[0], t.values[1], t.values[2]),
288
+ target_names=target_names[:3],
289
+ include_dominated_trials=False,
290
+ )
291
+ )
292
+
293
+ study_pane.append(param_importance(self.bench_cfg, study))
294
+ param_str.append(
295
+ f" Number of trials on the Pareto front: {len(study.best_trials)}"
296
+ )
297
+ for t in study.best_trials:
298
+ param_str.extend(summarise_trial(t, self.bench_cfg))
299
+
300
+ else:
301
+ # cols.append(plot_optimization_history(study)) #TODO, maybe more clever when this is plotted?
302
+
303
+ # If there is only 1 parameter then there is no point is plotting relative importance. Only worth plotting if there are multiple repeats of the same value so that you can compare the parameter vs to repeat to get a sense of the how much chance affects the results
304
+ # if bench_cfg.repeats > 1 and len(bench_cfg.input_vars) > 1: #old code, not sure if its right
305
+ if len(self.bench_cfg.input_vars) > 1:
306
+ study_pane.append(plot_param_importances(study, target_name=target_names[0]))
307
+
308
+ param_str.extend(summarise_trial(study.best_trial, self.bench_cfg))
309
+
310
+ kwargs = {"height": 500, "scroll": True} if len(param_str) > 30 else {}
311
+
312
+ param_str = "\n".join(param_str)
313
+ study_pane.append(
314
+ pn.Row(pn.pane.Markdown(f"## Best Parameters\n```text\n{param_str}"), **kwargs),
315
+ )
316
+
317
+ study_repeats_pane.append(study_pane)
318
+
319
+ return study_repeats_pane
320
+
321
+ # def extract_study_to_dataset(study: optuna.Study, bench_cfg: BenchCfg) -> BenchCfg:
322
+ # """Extract an optuna study into an xarray dataset for easy plotting
323
+
324
+ # Args:
325
+ # study (optuna.Study): The result of a gridsearch
326
+ # bench_cfg (BenchCfg): Options for the grid search
327
+
328
+ # Returns:
329
+ # BenchCfg: An updated config with the results included
330
+ # """
331
+ # for t in study.trials:
332
+ # for it, rv in enumerate(bench_cfg.result_vars):
333
+ # bench_cfg.ds[rv.name].loc[t.params] = t.values[it]
334
+ # return bench_cfg
335
+
336
+ def deep(self) -> OptunaResult: # pragma: no cover
337
+ """Return a deep copy of these results"""
338
+ return deepcopy(self)
339
+
340
+ def set_plot_size(self, **kwargs) -> dict:
341
+ if "width" not in kwargs:
342
+ if self.bench_cfg.plot_size is not None:
343
+ kwargs["width"] = self.bench_cfg.plot_size
344
+ # specific width overrrides general size
345
+ if self.bench_cfg.plot_width is not None:
346
+ kwargs["width"] = self.bench_cfg.plot_width
347
+
348
+ if "height" not in kwargs:
349
+ if self.bench_cfg.plot_size is not None:
350
+ kwargs["height"] = self.bench_cfg.plot_size
351
+ # specific height overrrides general size
352
+ if self.bench_cfg.plot_height is not None:
353
+ kwargs["height"] = self.bench_cfg.plot_height
354
+ return kwargs
@@ -0,0 +1,41 @@
1
+ from typing import Optional
2
+ from functools import partial
3
+ import panel as pn
4
+ from param import Parameter
5
+ import holoviews as hv
6
+ from bencher.results.bench_result_base import BenchResultBase, ReduceType
7
+ from bencher.results.video_result import VideoControls
8
+ from bencher.variables.results import (
9
+ PANEL_TYPES,
10
+ )
11
+
12
+
13
+ class PanelResult(BenchResultBase):
14
+ def to_video(self, result_var: Parameter = None, **kwargs):
15
+ vc = VideoControls()
16
+ return pn.Column(
17
+ vc.video_controls(),
18
+ self.to_panes(result_var=result_var, container=vc.video_container, **kwargs),
19
+ )
20
+
21
+ def to_panes(
22
+ self,
23
+ result_var: Parameter = None,
24
+ hv_dataset=None,
25
+ target_dimension: int = 0,
26
+ container=None,
27
+ level: int = None,
28
+ **kwargs,
29
+ ) -> Optional[pn.pane.panel]:
30
+ if hv_dataset is None:
31
+ hv_dataset = self.to_hv_dataset(ReduceType.SQUEEZE, level=level)
32
+ elif not isinstance(hv_dataset, hv.Dataset):
33
+ hv_dataset = hv.Dataset(hv_dataset)
34
+ return self.map_plot_panes(
35
+ partial(self.ds_to_container, container=container),
36
+ hv_dataset=hv_dataset,
37
+ target_dimension=target_dimension,
38
+ result_var=result_var,
39
+ result_types=PANEL_TYPES,
40
+ **kwargs,
41
+ )
@@ -0,0 +1,65 @@
1
+ import panel as pn
2
+ import plotly.graph_objs as go
3
+ from typing import Optional
4
+ import xarray as xr
5
+
6
+ from param import Parameter
7
+
8
+ from bencher.plotting.plot_filter import VarRange
9
+ from bencher.results.bench_result_base import BenchResultBase, ReduceType
10
+ from bencher.variables.results import ResultVar
11
+
12
+
13
+ class PlotlyResult(BenchResultBase):
14
+ def to_volume(self, result_var: Parameter = None, **kwargs):
15
+ return self.filter(
16
+ self.to_volume_da,
17
+ float_range=VarRange(3, 3),
18
+ cat_range=VarRange(-1, 0),
19
+ reduce=ReduceType.REDUCE,
20
+ target_dimension=3,
21
+ result_var=result_var,
22
+ result_types=(ResultVar),
23
+ **kwargs,
24
+ )
25
+
26
+ def to_volume_da(
27
+ self, dataset: xr.Dataset, result_var: Parameter, width=600, height=600
28
+ ) -> Optional[pn.pane.Plotly]:
29
+ """Given a benchCfg generate a 3D surface plot
30
+ Returns:
31
+ pn.pane.Plotly: A 3d volume plot as a holoview in a pane
32
+ """
33
+ x = self.bench_cfg.input_vars[0]
34
+ y = self.bench_cfg.input_vars[1]
35
+ z = self.bench_cfg.input_vars[2]
36
+ opacity = 0.1
37
+ meandf = dataset[result_var.name].to_dataframe().reset_index()
38
+ data = [
39
+ go.Volume(
40
+ x=meandf[x.name],
41
+ y=meandf[y.name],
42
+ z=meandf[z.name],
43
+ value=meandf[result_var.name],
44
+ isomin=meandf[result_var.name].min(),
45
+ isomax=meandf[result_var.name].max(),
46
+ opacity=opacity,
47
+ surface_count=20,
48
+ )
49
+ ]
50
+
51
+ layout = go.Layout(
52
+ title=f"{result_var.name} vs ({x.name} vs {y.name} vs {z.name})",
53
+ width=width,
54
+ height=height,
55
+ margin=dict(t=50, b=50, r=50, l=50),
56
+ scene=dict(
57
+ xaxis_title=f"{x.name} [{x.units}]",
58
+ yaxis_title=f"{y.name} [{y.units}]",
59
+ zaxis_title=f"{z.name} [{z.units}]",
60
+ ),
61
+ )
62
+
63
+ fig = dict(data=data, layout=layout)
64
+
65
+ return pn.pane.Plotly(fig, name="volume_plotly")
@@ -0,0 +1,38 @@
1
+ from typing import Optional
2
+ from pathlib import Path
3
+ import panel as pn
4
+
5
+
6
+ class VideoControls:
7
+ def __init__(self) -> None:
8
+ self.vid_p = []
9
+
10
+ def video_container(self, path, **kwargs):
11
+ if path is not None and Path(path).exists():
12
+ vid = pn.pane.Video(path, autoplay=True, **kwargs)
13
+ vid.loop = True
14
+ self.vid_p.append(vid)
15
+ return vid
16
+ return pn.pane.Markdown(f"video does not exist {path}")
17
+
18
+ def video_controls(self) -> Optional[pn.Column]:
19
+ def play_vid(_): # pragma: no cover
20
+ for r in self.vid_p:
21
+ r.paused = False
22
+
23
+ def reset_vid(_): # pragma: no cover
24
+ for r in self.vid_p:
25
+ r.paused = False
26
+ r.time = 0
27
+
28
+ button_names = ["Play Videos", "Pause Videos", "Loop Videos", "Reset Videos"]
29
+ buttom_cb = [play_vid, reset_vid]
30
+
31
+ buttons = pn.Row()
32
+
33
+ for name, cb in zip(button_names, buttom_cb):
34
+ button = pn.widgets.Button(name=name)
35
+ pn.bind(cb, button, watch=True)
36
+ buttons.append(button)
37
+
38
+ return pn.Column(buttons)