holobench 1.5.0__py2.py3-none-any.whl → 1.7.0__py2.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.
- bencher/bench_cfg.py +5 -0
- bencher/bench_plot_server.py +1 -0
- bencher/bench_report.py +14 -14
- bencher/bench_runner.py +1 -1
- bencher/bencher.py +46 -33
- bencher/example/example_float_cat.py +1 -0
- bencher/example/example_image.py +18 -31
- bencher/example/example_simple_bool.py +1 -0
- bencher/example/example_simple_cat.py +1 -0
- bencher/example/example_time_event.py +1 -0
- bencher/results/bench_result.py +9 -2
- bencher/results/bench_result_base.py +55 -44
- bencher/results/composable_container/__init__.py +0 -0
- bencher/results/composable_container/composable_container_base.py +58 -0
- bencher/results/composable_container/composable_container_panel.py +40 -0
- bencher/results/composable_container/composable_container_video.py +54 -0
- bencher/results/panel_result.py +1 -32
- bencher/results/video_summary.py +129 -8
- bencher/utils.py +2 -2
- bencher/variables/parametrised_sweep.py +18 -1
- bencher/video_writer.py +46 -22
- {holobench-1.5.0.dist-info → holobench-1.7.0.dist-info}/METADATA +13 -13
- {holobench-1.5.0.dist-info → holobench-1.7.0.dist-info}/RECORD +24 -20
- {holobench-1.5.0.dist-info → holobench-1.7.0.dist-info}/WHEEL +0 -0
bencher/bench_cfg.py
CHANGED
@@ -303,6 +303,11 @@ class BenchCfg(BenchRunCfg):
|
|
303
303
|
doc="store the hash value of the config to avoid having to hash multiple times",
|
304
304
|
)
|
305
305
|
|
306
|
+
plot_callbacks = param.List(
|
307
|
+
None,
|
308
|
+
doc="A callable that takes a BenchResult and returns panel representation of the results",
|
309
|
+
)
|
310
|
+
|
306
311
|
def __init__(self, **params):
|
307
312
|
super().__init__(**params)
|
308
313
|
self.plot_lib = None
|
bencher/bench_plot_server.py
CHANGED
bencher/bench_report.py
CHANGED
@@ -3,7 +3,8 @@ from typing import Callable
|
|
3
3
|
import os
|
4
4
|
import panel as pn
|
5
5
|
from pathlib import Path
|
6
|
-
import
|
6
|
+
import tempfile
|
7
|
+
|
7
8
|
from threading import Thread
|
8
9
|
|
9
10
|
from bencher.results.bench_result import BenchResult
|
@@ -47,7 +48,7 @@ class BenchReport(BenchPlotServer):
|
|
47
48
|
self.pane.append(col)
|
48
49
|
|
49
50
|
def append_result(self, bench_res: BenchResult) -> None:
|
50
|
-
self.append_tab(bench_res.
|
51
|
+
self.append_tab(bench_res.plot(), bench_res.bench_cfg.title)
|
51
52
|
|
52
53
|
def append_tab(self, pane: pn.panel, name: str = None) -> None:
|
53
54
|
if pane is not None:
|
@@ -137,24 +138,23 @@ class BenchReport(BenchPlotServer):
|
|
137
138
|
|
138
139
|
remote, publish_url = remote_callback(branch_name)
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
with tempfile.TemporaryDirectory() as td:
|
142
|
+
directory = td
|
143
|
+
report_path = self.save(directory, filename="index.html", in_html_folder=False)
|
144
|
+
logging.info(f"created report at: {report_path.absolute()}")
|
143
145
|
|
144
|
-
|
146
|
+
cd_dir = f"cd {directory} &&"
|
145
147
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
148
|
+
os.system(f"{cd_dir} git init")
|
149
|
+
os.system(f"{cd_dir} git checkout -b {branch_name}")
|
150
|
+
os.system(f"{cd_dir} git add index.html")
|
151
|
+
os.system(f'{cd_dir} git commit -m "publish {branch_name}"')
|
152
|
+
os.system(f"{cd_dir} git remote add origin {remote}")
|
153
|
+
os.system(f"{cd_dir} git push --set-upstream origin {branch_name} -f")
|
152
154
|
|
153
155
|
logging.info("Published report @")
|
154
156
|
logging.info(publish_url)
|
155
157
|
|
156
|
-
shutil.rmtree(directory)
|
157
|
-
|
158
158
|
return publish_url
|
159
159
|
|
160
160
|
|
bencher/bench_runner.py
CHANGED
@@ -103,7 +103,7 @@ class BenchRunner:
|
|
103
103
|
for r in range(1, repeats + 1):
|
104
104
|
for lvl in range(min_level, max_level + 1):
|
105
105
|
if grouped:
|
106
|
-
report_level = BenchReport(self.name)
|
106
|
+
report_level = BenchReport(f"{self.name}_{run_cfg.run_tag}")
|
107
107
|
|
108
108
|
for bch_fn in self.bench_fns:
|
109
109
|
run_lvl = deepcopy(run_cfg)
|
bencher/bencher.py
CHANGED
@@ -10,6 +10,7 @@ import xarray as xr
|
|
10
10
|
from diskcache import Cache
|
11
11
|
from contextlib import suppress
|
12
12
|
from functools import partial
|
13
|
+
import panel as pn
|
13
14
|
|
14
15
|
from bencher.worker_job import WorkerJob
|
15
16
|
|
@@ -165,6 +166,17 @@ class Bench(BenchPlotServer):
|
|
165
166
|
|
166
167
|
self.cache_size = int(100e9) # default to 100gb
|
167
168
|
|
169
|
+
# self.bench_cfg = BenchCfg()
|
170
|
+
|
171
|
+
# Maybe put this in SweepCfg
|
172
|
+
self.input_vars = None
|
173
|
+
self.result_vars = None
|
174
|
+
self.const_vars = None
|
175
|
+
self.plot_callbacks = []
|
176
|
+
|
177
|
+
def add_plot_callback(self, callback: Callable[[BenchResult], pn.panel]) -> None:
|
178
|
+
self.plot_callbacks.append(callback)
|
179
|
+
|
168
180
|
def set_worker(self, worker: Callable, worker_input_cfg: ParametrizedSweep = None) -> None:
|
169
181
|
"""Set the benchmark worker function and optionally the type the worker expects
|
170
182
|
|
@@ -188,34 +200,6 @@ class Bench(BenchPlotServer):
|
|
188
200
|
logging.info(f"setting worker {worker}")
|
189
201
|
self.worker_input_cfg = worker_input_cfg
|
190
202
|
|
191
|
-
def sweep(
|
192
|
-
self,
|
193
|
-
input_vars: List[ParametrizedSweep] = None,
|
194
|
-
result_vars: List[ParametrizedSweep] = None,
|
195
|
-
const_vars: List[ParametrizedSweep] = None,
|
196
|
-
time_src: datetime = None,
|
197
|
-
description: str = None,
|
198
|
-
post_description: str = None,
|
199
|
-
pass_repeat: bool = False,
|
200
|
-
tag: str = "",
|
201
|
-
run_cfg: BenchRunCfg = None,
|
202
|
-
plot: bool = False,
|
203
|
-
) -> BenchResult:
|
204
|
-
title = "Sweeping " + " vs ".join(params_to_str(input_vars))
|
205
|
-
return self.plot_sweep(
|
206
|
-
title,
|
207
|
-
input_vars=input_vars,
|
208
|
-
result_vars=result_vars,
|
209
|
-
const_vars=const_vars,
|
210
|
-
time_src=time_src,
|
211
|
-
description=description,
|
212
|
-
post_description=post_description,
|
213
|
-
pass_repeat=pass_repeat,
|
214
|
-
tag=tag,
|
215
|
-
run_cfg=run_cfg,
|
216
|
-
plot=plot,
|
217
|
-
)
|
218
|
-
|
219
203
|
def sweep_sequential(
|
220
204
|
self,
|
221
205
|
title="",
|
@@ -264,6 +248,7 @@ class Bench(BenchPlotServer):
|
|
264
248
|
tag: str = "",
|
265
249
|
run_cfg: BenchRunCfg = None,
|
266
250
|
plot: bool = True,
|
251
|
+
plot_callbacks=None,
|
267
252
|
) -> BenchResult:
|
268
253
|
"""The all in 1 function benchmarker and results plotter.
|
269
254
|
|
@@ -278,7 +263,8 @@ class Bench(BenchPlotServer):
|
|
278
263
|
pass_repeat (bool,optional) By default do not pass the kwarg 'repeat' to the benchmark function. Set to true if
|
279
264
|
you want the benchmark function to be passed the repeat number
|
280
265
|
tag (str,optional): Use tags to group different benchmarks together.
|
281
|
-
run_cfg: (BenchRunCfg, optional): A config for storing how the benchmarks and run
|
266
|
+
run_cfg: (BenchRunCfg, optional): A config for storing how the benchmarks and run
|
267
|
+
plot_callbacks: A list of plot callbacks to clal on the results
|
282
268
|
Raises:
|
283
269
|
ValueError: If a result variable is not set
|
284
270
|
|
@@ -291,18 +277,27 @@ class Bench(BenchPlotServer):
|
|
291
277
|
logging.info(
|
292
278
|
"No input variables passed, using all param variables in bench class as inputs"
|
293
279
|
)
|
294
|
-
input_vars
|
280
|
+
if self.input_vars is None:
|
281
|
+
input_vars = self.worker_class_instance.get_inputs_only()
|
282
|
+
else:
|
283
|
+
input_vars = self.input_vars
|
295
284
|
for i in input_vars:
|
296
285
|
logging.info(f"input var: {i.name}")
|
297
286
|
if result_vars is None:
|
298
287
|
logging.info(
|
299
288
|
"No results variables passed, using all result variables in bench class:"
|
300
289
|
)
|
301
|
-
result_vars
|
290
|
+
if self.result_vars is None:
|
291
|
+
result_vars = self.worker_class_instance.get_results_only()
|
292
|
+
else:
|
293
|
+
result_vars = self.result_vars
|
302
294
|
for r in result_vars:
|
303
295
|
logging.info(f"result var: {r.name}")
|
304
296
|
if const_vars is None:
|
305
|
-
const_vars
|
297
|
+
if self.const_vars is None:
|
298
|
+
const_vars = self.worker_class_instance.get_input_defaults()
|
299
|
+
else:
|
300
|
+
const_vars = self.const_vars
|
306
301
|
else:
|
307
302
|
if input_vars is None:
|
308
303
|
input_vars = []
|
@@ -381,6 +376,12 @@ class Bench(BenchPlotServer):
|
|
381
376
|
"## Results Description\nPlease set post_description to explain these results"
|
382
377
|
)
|
383
378
|
|
379
|
+
if plot_callbacks is None:
|
380
|
+
if len(self.plot_callbacks) == 0:
|
381
|
+
plot_callbacks = [BenchResult.to_auto_plots]
|
382
|
+
else:
|
383
|
+
plot_callbacks = self.plot_callbacks
|
384
|
+
|
384
385
|
bench_cfg = BenchCfg(
|
385
386
|
input_vars=input_vars,
|
386
387
|
result_vars=result_vars_only,
|
@@ -392,7 +393,14 @@ class Bench(BenchPlotServer):
|
|
392
393
|
title=title,
|
393
394
|
pass_repeat=pass_repeat,
|
394
395
|
tag=run_cfg.run_tag + tag,
|
396
|
+
auto_plot=plot,
|
397
|
+
plot_callbacks=plot_callbacks,
|
395
398
|
)
|
399
|
+
return self.run_sweep(bench_cfg, run_cfg, time_src)
|
400
|
+
|
401
|
+
def run_sweep(
|
402
|
+
self, bench_cfg: BenchCfg, run_cfg: BenchRunCfg, time_src: datetime
|
403
|
+
) -> BenchResult:
|
396
404
|
print("tag", bench_cfg.tag)
|
397
405
|
|
398
406
|
bench_cfg.param.update(run_cfg.param.values())
|
@@ -447,8 +455,9 @@ class Bench(BenchPlotServer):
|
|
447
455
|
|
448
456
|
bench_res.post_setup()
|
449
457
|
|
450
|
-
if
|
458
|
+
if bench_cfg.auto_plot:
|
451
459
|
self.report.append_result(bench_res)
|
460
|
+
|
452
461
|
self.results.append(bench_res)
|
453
462
|
return bench_res
|
454
463
|
|
@@ -799,3 +808,7 @@ class Bench(BenchPlotServer):
|
|
799
808
|
|
800
809
|
def get_result(self, index: int = -1) -> BenchResult:
|
801
810
|
return self.results[index]
|
811
|
+
|
812
|
+
def publish(self, remote_callback: Callable) -> str:
|
813
|
+
branch_name = f"{self.bench_name}_{self.run_cfg.run_tag}"
|
814
|
+
return self.report.publish(remote_callback, branch_name=branch_name)
|
bencher/example/example_image.py
CHANGED
@@ -44,7 +44,7 @@ class BenchPolygons(bch.ParametrizedSweep):
|
|
44
44
|
|
45
45
|
ax.set_aspect("equal")
|
46
46
|
fig.add_axes(ax)
|
47
|
-
fig.savefig(filename, dpi=
|
47
|
+
fig.savefig(filename, dpi=30)
|
48
48
|
|
49
49
|
return filename
|
50
50
|
|
@@ -54,24 +54,9 @@ def example_image(
|
|
54
54
|
) -> bch.Bench:
|
55
55
|
bench = bch.Bench("polygons", BenchPolygons(), run_cfg=run_cfg, report=report)
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
[
|
60
|
-
[BenchPolygons.param.sides, BenchPolygons.param.linewidth, BenchPolygons.param.linestyle],
|
61
|
-
[
|
62
|
-
BenchPolygons.param.sides,
|
63
|
-
BenchPolygons.param.linewidth,
|
64
|
-
BenchPolygons.param.linestyle,
|
65
|
-
BenchPolygons.param.color,
|
66
|
-
],
|
67
|
-
[
|
68
|
-
BenchPolygons.param.sides,
|
69
|
-
BenchPolygons.param.linewidth,
|
70
|
-
BenchPolygons.param.linestyle,
|
71
|
-
BenchPolygons.param.color,
|
72
|
-
BenchPolygons.param.radius,
|
73
|
-
],
|
74
|
-
]:
|
57
|
+
sweep_vars = ["sides", "radius", "linewidth", "color"]
|
58
|
+
for i in range(1, len(sweep_vars)):
|
59
|
+
s = sweep_vars[:i]
|
75
60
|
bench.plot_sweep(
|
76
61
|
f"Polygons Sweeping {len(s)} Parameters",
|
77
62
|
input_vars=s,
|
@@ -81,24 +66,26 @@ def example_image(
|
|
81
66
|
return bench
|
82
67
|
|
83
68
|
|
84
|
-
|
69
|
+
def example_image_vid(
|
70
|
+
run_cfg: bch.BenchRunCfg = bch.BenchRunCfg(), report: bch.BenchReport = bch.BenchReport()
|
71
|
+
) -> bch.Bench:
|
72
|
+
bench = BenchPolygons().to_bench(run_cfg, report)
|
73
|
+
bench.add_plot_callback(bch.BenchResult.to_sweep_summary)
|
74
|
+
bench.add_plot_callback(bch.BenchResult.to_video_grid)
|
75
|
+
bench.plot_sweep(input_vars=["sides"])
|
76
|
+
bench.plot_sweep(input_vars=["sides", "radius"])
|
77
|
+
return bench
|
85
78
|
|
86
|
-
|
87
|
-
|
88
|
-
) -> bch.Bench:
|
89
|
-
bench = BenchPolygons().to_bench(run_cfg, report)
|
90
|
-
bench.plot_sweep(input_vars=["sides", "radius", "color"], plot=True)
|
91
|
-
return bench
|
79
|
+
|
80
|
+
if __name__ == "__main__":
|
92
81
|
|
93
82
|
def example_image_vid_sequential(
|
94
83
|
run_cfg: bch.BenchRunCfg = bch.BenchRunCfg(), report: bch.BenchReport = bch.BenchReport()
|
95
84
|
) -> bch.Bench:
|
96
85
|
bench = BenchPolygons().to_bench(run_cfg, report)
|
97
|
-
|
98
|
-
|
99
|
-
)
|
100
|
-
for r in res_list:
|
101
|
-
bench.report.append(r.to_video_summary())
|
86
|
+
bench.add_plot_callback(bch.BenchResult.to_title)
|
87
|
+
bench.add_plot_callback(bch.BenchResult.to_video_grid)
|
88
|
+
bench.sweep_sequential(input_vars=["radius", "sides", "linewidth", "color"], group_size=1)
|
102
89
|
return bench
|
103
90
|
|
104
91
|
# def example_image_pairs()
|
bencher/results/bench_result.py
CHANGED
@@ -11,7 +11,6 @@ from bencher.utils import listify
|
|
11
11
|
|
12
12
|
|
13
13
|
class BenchResult(PlotlyResult, HoloviewResult, VideoSummaryResult):
|
14
|
-
|
15
14
|
"""Contains the results of the benchmark and has methods to cast the results to various datatypes and graphical representations"""
|
16
15
|
|
17
16
|
def __init__(self, bench_cfg) -> None:
|
@@ -36,6 +35,14 @@ class BenchResult(PlotlyResult, HoloviewResult, VideoSummaryResult):
|
|
36
35
|
def plotly_callbacks():
|
37
36
|
return [HoloviewResult.to_surface, PlotlyResult.to_volume]
|
38
37
|
|
38
|
+
def plot(self) -> pn.panel:
|
39
|
+
"""Plots the benchresult using the plot callbacks defined by the bench run
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
pn.panel: A panel representation of the results
|
43
|
+
"""
|
44
|
+
return pn.Column(*[cb(self) for cb in self.bench_cfg.plot_callbacks])
|
45
|
+
|
39
46
|
def to_auto(
|
40
47
|
self,
|
41
48
|
plot_list: List[callable] = None,
|
@@ -68,7 +75,7 @@ class BenchResult(PlotlyResult, HoloviewResult, VideoSummaryResult):
|
|
68
75
|
)
|
69
76
|
return row.pane
|
70
77
|
|
71
|
-
def to_auto_plots(self, **kwargs) ->
|
78
|
+
def to_auto_plots(self, **kwargs) -> pn.panel:
|
72
79
|
"""Given the dataset result of a benchmark run, automatically dedeuce how to plot the data based on the types of variables that were sampled
|
73
80
|
|
74
81
|
Args:
|
@@ -5,6 +5,8 @@ import xarray as xr
|
|
5
5
|
from param import Parameter
|
6
6
|
import holoviews as hv
|
7
7
|
from functools import partial
|
8
|
+
import panel as pn
|
9
|
+
|
8
10
|
from bencher.utils import int_to_col, color_tuple_to_css, callable_name
|
9
11
|
|
10
12
|
from bencher.variables.parametrised_sweep import ParametrizedSweep
|
@@ -12,10 +14,13 @@ from bencher.variables.results import OptDir
|
|
12
14
|
from copy import deepcopy
|
13
15
|
from bencher.results.optuna_result import OptunaResult
|
14
16
|
from bencher.variables.results import ResultVar
|
15
|
-
from bencher.results.float_formatter import FormatFloat
|
16
17
|
from bencher.plotting.plot_filter import VarRange, PlotFilter
|
17
|
-
import panel as pn
|
18
18
|
|
19
|
+
from bencher.variables.results import (
|
20
|
+
ResultReference,
|
21
|
+
)
|
22
|
+
|
23
|
+
from bencher.results.composable_container.composable_container_panel import ComposableContainerPanel
|
19
24
|
|
20
25
|
# todo add plugins
|
21
26
|
# https://gist.github.com/dorneanu/cce1cd6711969d581873a88e0257e312
|
@@ -333,29 +338,24 @@ class BenchResultBase(OptunaResult):
|
|
333
338
|
time_dim_delta = 0
|
334
339
|
|
335
340
|
if num_dims > (target_dimension + time_dim_delta) and num_dims != 0:
|
336
|
-
|
341
|
+
selected_dim = dims[-1]
|
337
342
|
# print(f"selected dim {dim_sel}")
|
338
|
-
|
339
343
|
dim_color = color_tuple_to_css(int_to_col(num_dims - 2, 0.05, 1.0))
|
340
344
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
container_args = {"name": name, "styles": {"background": background_col}}
|
345
|
-
outer_container = (
|
346
|
-
pn.Row(**container_args) if horizontal else pn.Column(**container_args)
|
345
|
+
outer_container = ComposableContainerPanel(
|
346
|
+
name=" vs ".join(dims), background_col=dim_color, horizontal=not horizontal
|
347
347
|
)
|
348
|
-
|
349
348
|
max_len = 0
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
349
|
+
for i in range(dataset.sizes[selected_dim]):
|
350
|
+
sliced = dataset.isel({selected_dim: i})
|
351
|
+
label_val = sliced.coords[selected_dim].values.item()
|
352
|
+
inner_container = ComposableContainerPanel(
|
353
|
+
outer_container.name,
|
354
|
+
width=num_dims - target_dimension,
|
355
|
+
var_name=selected_dim,
|
356
|
+
var_value=label_val,
|
357
|
+
horizontal=horizontal,
|
358
|
+
)
|
359
359
|
|
360
360
|
panes = self._to_panes_da(
|
361
361
|
sliced,
|
@@ -364,35 +364,46 @@ class BenchResultBase(OptunaResult):
|
|
364
364
|
horizontal=len(sliced.sizes) <= target_dimension + 1,
|
365
365
|
result_var=result_var,
|
366
366
|
)
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
"name": name,
|
371
|
-
"styles": {"border-bottom": f"{width}px solid grey"},
|
372
|
-
}
|
373
|
-
|
374
|
-
if horizontal:
|
375
|
-
inner_container = pn.Column(**container_args)
|
376
|
-
align = ("center", "center")
|
377
|
-
else:
|
378
|
-
inner_container = pn.Row(**container_args)
|
379
|
-
align = ("end", "center")
|
380
|
-
|
381
|
-
label_len = len(label)
|
382
|
-
if label_len > max_len:
|
383
|
-
max_len = label_len
|
384
|
-
side = pn.pane.Markdown(label, align=align)
|
385
|
-
|
386
|
-
inner_container.append(side)
|
367
|
+
|
368
|
+
if inner_container.label_len > max_len:
|
369
|
+
max_len = inner_container.label_len
|
387
370
|
inner_container.append(panes)
|
388
|
-
outer_container.append(inner_container)
|
389
|
-
|
390
|
-
for c in outer_container:
|
371
|
+
outer_container.append(inner_container.container)
|
372
|
+
for c in outer_container.container:
|
391
373
|
c[0].width = max_len * 7
|
392
374
|
else:
|
393
375
|
return plot_callback(dataset=dataset, result_var=result_var, **kwargs)
|
394
376
|
|
395
|
-
return outer_container
|
377
|
+
return outer_container.container
|
378
|
+
|
379
|
+
def zero_dim_da_to_val(self, da_ds: xr.DataArray | xr.Dataset) -> Any:
|
380
|
+
# todo this is really horrible, need to improve
|
381
|
+
dim = None
|
382
|
+
if isinstance(da_ds, xr.Dataset):
|
383
|
+
dim = list(da_ds.keys())[0]
|
384
|
+
da = da_ds[dim]
|
385
|
+
else:
|
386
|
+
da = da_ds
|
387
|
+
|
388
|
+
for k in da.coords.keys():
|
389
|
+
dim = k
|
390
|
+
break
|
391
|
+
if dim is None:
|
392
|
+
return da_ds.values.squeeze().item()
|
393
|
+
return da.expand_dims(dim).values[0]
|
394
|
+
|
395
|
+
def ds_to_container(
|
396
|
+
self, dataset: xr.Dataset, result_var: Parameter, container, **kwargs
|
397
|
+
) -> Any:
|
398
|
+
val = self.zero_dim_da_to_val(dataset[result_var.name])
|
399
|
+
if isinstance(result_var, ResultReference):
|
400
|
+
ref = self.object_index[val]
|
401
|
+
val = ref.obj
|
402
|
+
if ref.container is not None:
|
403
|
+
return ref.container(val, **kwargs)
|
404
|
+
if container is not None:
|
405
|
+
return container(val, styles={"background": "white"}, **kwargs)
|
406
|
+
return val
|
396
407
|
|
397
408
|
# MAPPING TO LOWER LEVEL BENCHCFG functions so they are available at a top level.
|
398
409
|
def to_sweep_summary(self, **kwargs):
|
File without changes
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from enum import Enum, auto
|
2
|
+
from typing import Any
|
3
|
+
from bencher.results.float_formatter import FormatFloat
|
4
|
+
|
5
|
+
|
6
|
+
# TODO enable these options
|
7
|
+
class ComposeType(Enum):
|
8
|
+
right = auto() # append the container to the right (creates a row)
|
9
|
+
down = auto() # append the container below (creates a column)
|
10
|
+
overlay = auto() # overlay on top of the current container (alpha blending)
|
11
|
+
sequence = auto() # display the container after (in time)
|
12
|
+
|
13
|
+
|
14
|
+
class ComposableContainerBase:
|
15
|
+
"""A base class for renderer backends. A composable renderr"""
|
16
|
+
|
17
|
+
@staticmethod
|
18
|
+
def label_formatter(var_name: str, var_value: int | float | str) -> str:
|
19
|
+
"""Take a variable name and values and return a pretty version with approximate fixed width
|
20
|
+
|
21
|
+
Args:
|
22
|
+
var_name (str): The name of the variable, usually a dimension
|
23
|
+
var_value (int | float | str): The value of the dimension
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
str: Pretty string representation with fixed width
|
27
|
+
"""
|
28
|
+
|
29
|
+
if isinstance(var_value, (int, float)):
|
30
|
+
var_value = FormatFloat()(var_value)
|
31
|
+
if var_name is not None and var_value is not None:
|
32
|
+
return f"{var_name}={var_value}"
|
33
|
+
if var_name is not None:
|
34
|
+
return f"{var_name}"
|
35
|
+
if var_value is not None:
|
36
|
+
return f"{var_value}"
|
37
|
+
return None
|
38
|
+
|
39
|
+
def __init__(self, horizontal: bool = True) -> None:
|
40
|
+
self.horizontal: bool = horizontal
|
41
|
+
self.compose_method = ComposeType.right
|
42
|
+
self.container = []
|
43
|
+
|
44
|
+
def append(self, obj: Any) -> None:
|
45
|
+
"""Add an object to the container. The relationship between the objects is defined by the ComposeType
|
46
|
+
|
47
|
+
Args:
|
48
|
+
obj (Any): Object to add to the container
|
49
|
+
"""
|
50
|
+
self.container.append(obj)
|
51
|
+
|
52
|
+
def render(self):
|
53
|
+
"""Return a representation of the container that can be composed with other render() results. This function can also be used to defer layout and rending options until all the information about the container content is known. You may need to ovverride this method depending on the container. See composable_container_video as an example.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
Any: Visual representation of the container that can be combined with other containers
|
57
|
+
"""
|
58
|
+
return self.container
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import panel as pn
|
2
|
+
from bencher.results.composable_container.composable_container_base import ComposableContainerBase
|
3
|
+
|
4
|
+
|
5
|
+
class ComposableContainerPanel(ComposableContainerBase):
|
6
|
+
def __init__(
|
7
|
+
self,
|
8
|
+
name: str = None,
|
9
|
+
var_name: str = None,
|
10
|
+
var_value: str = None,
|
11
|
+
width: int = None,
|
12
|
+
background_col: str = None,
|
13
|
+
horizontal: bool = True,
|
14
|
+
) -> None:
|
15
|
+
super().__init__(horizontal)
|
16
|
+
|
17
|
+
container_args = {
|
18
|
+
"name": name,
|
19
|
+
"styles": {},
|
20
|
+
}
|
21
|
+
|
22
|
+
self.name = name
|
23
|
+
|
24
|
+
if width is not None:
|
25
|
+
container_args["styles"]["border-bottom"] = f"{width}px solid grey"
|
26
|
+
if background_col is not None:
|
27
|
+
container_args["styles"]["background"] = background_col
|
28
|
+
|
29
|
+
if horizontal:
|
30
|
+
self.container = pn.Column(**container_args)
|
31
|
+
align = ("center", "center")
|
32
|
+
else:
|
33
|
+
self.container = pn.Row(**container_args)
|
34
|
+
align = ("end", "center")
|
35
|
+
|
36
|
+
label = self.label_formatter(var_name, var_value)
|
37
|
+
if label is not None:
|
38
|
+
self.label_len = len(label)
|
39
|
+
side = pn.pane.Markdown(label, align=align)
|
40
|
+
self.append(side)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import numpy as np
|
2
|
+
from moviepy.editor import (
|
3
|
+
ImageClip,
|
4
|
+
CompositeVideoClip,
|
5
|
+
clips_array,
|
6
|
+
concatenate_videoclips,
|
7
|
+
VideoClip,
|
8
|
+
)
|
9
|
+
|
10
|
+
from bencher.results.composable_container.composable_container_base import ComposableContainerBase
|
11
|
+
from bencher.video_writer import VideoWriter
|
12
|
+
|
13
|
+
|
14
|
+
class ComposableContainerVideo(ComposableContainerBase):
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
name: str = None,
|
18
|
+
var_name: str = None,
|
19
|
+
var_value: str = None,
|
20
|
+
background_col: tuple[3] = (255, 255, 255),
|
21
|
+
horizontal: bool = True,
|
22
|
+
) -> None:
|
23
|
+
super().__init__(horizontal)
|
24
|
+
self.name = name
|
25
|
+
self.container = []
|
26
|
+
self.background_col = background_col
|
27
|
+
|
28
|
+
self.label = self.label_formatter(var_name, var_value)
|
29
|
+
if self.label is not None:
|
30
|
+
self.label_len = len(self.label)
|
31
|
+
|
32
|
+
def append(self, obj: VideoClip | str) -> None:
|
33
|
+
if isinstance(obj, VideoClip):
|
34
|
+
self.container.append(obj)
|
35
|
+
else:
|
36
|
+
if self.label is not None:
|
37
|
+
img_obj = np.array(VideoWriter.label_image(obj, self.label))
|
38
|
+
else:
|
39
|
+
img_obj = obj
|
40
|
+
self.container.append(ImageClip(img_obj, duration=1.0))
|
41
|
+
|
42
|
+
def render(self, concatenate: bool = False) -> CompositeVideoClip:
|
43
|
+
if concatenate:
|
44
|
+
return concatenate_videoclips(self.container)
|
45
|
+
if self.horizontal:
|
46
|
+
# if self.label is not None:
|
47
|
+
# width = self.label_len*6
|
48
|
+
# img = VideoWriter.create_label(self.label, width=width)
|
49
|
+
# side = ImageClip(np.array(img), duration=1.0)
|
50
|
+
# self.container.insert(0, side)
|
51
|
+
clips = [self.container]
|
52
|
+
else:
|
53
|
+
clips = [[c] for c in self.container]
|
54
|
+
return clips_array(clips, bg_color=self.background_col)
|
bencher/results/panel_result.py
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
from typing import Optional
|
1
|
+
from typing import Optional
|
2
2
|
from functools import partial
|
3
3
|
import panel as pn
|
4
|
-
import xarray as xr
|
5
4
|
from param import Parameter
|
6
5
|
from bencher.results.bench_result_base import BenchResultBase, ReduceType
|
7
6
|
from bencher.results.video_result import VideoControls
|
8
7
|
from bencher.variables.results import (
|
9
|
-
ResultReference,
|
10
8
|
PANEL_TYPES,
|
11
9
|
)
|
12
10
|
|
@@ -19,35 +17,6 @@ class PanelResult(BenchResultBase):
|
|
19
17
|
self.to_panes(result_var=result_var, container=vc.video_container, **kwargs),
|
20
18
|
)
|
21
19
|
|
22
|
-
def zero_dim_da_to_val(self, da_ds: xr.DataArray | xr.Dataset) -> Any:
|
23
|
-
# todo this is really horrible, need to improve
|
24
|
-
dim = None
|
25
|
-
if isinstance(da_ds, xr.Dataset):
|
26
|
-
dim = list(da_ds.keys())[0]
|
27
|
-
da = da_ds[dim]
|
28
|
-
else:
|
29
|
-
da = da_ds
|
30
|
-
|
31
|
-
for k in da.coords.keys():
|
32
|
-
dim = k
|
33
|
-
break
|
34
|
-
if dim is None:
|
35
|
-
return da_ds.values.squeeze().item()
|
36
|
-
return da.expand_dims(dim).values[0]
|
37
|
-
|
38
|
-
def ds_to_container(
|
39
|
-
self, dataset: xr.Dataset, result_var: Parameter, container, **kwargs
|
40
|
-
) -> Any:
|
41
|
-
val = self.zero_dim_da_to_val(dataset[result_var.name])
|
42
|
-
if isinstance(result_var, ResultReference):
|
43
|
-
ref = self.object_index[val]
|
44
|
-
val = ref.obj
|
45
|
-
if ref.container is not None:
|
46
|
-
return ref.container(val, **kwargs)
|
47
|
-
if container is not None:
|
48
|
-
return container(val, styles={"background": "white"}, **kwargs)
|
49
|
-
return val
|
50
|
-
|
51
20
|
def to_panes(
|
52
21
|
self, result_var: Parameter = None, target_dimension: int = 0, container=None, **kwargs
|
53
22
|
) -> Optional[pn.pane.panel]:
|
bencher/results/video_summary.py
CHANGED
@@ -5,12 +5,14 @@ import panel as pn
|
|
5
5
|
import xarray as xr
|
6
6
|
from param import Parameter
|
7
7
|
from bencher.results.bench_result_base import BenchResultBase, ReduceType
|
8
|
-
from bencher.variables.results import ResultImage
|
8
|
+
from bencher.variables.results import ResultImage
|
9
9
|
from bencher.plotting.plot_filter import VarRange, PlotFilter
|
10
10
|
from bencher.utils import callable_name, listify
|
11
11
|
from bencher.video_writer import VideoWriter
|
12
12
|
from bencher.results.float_formatter import FormatFloat
|
13
13
|
from bencher.results.video_result import VideoControls
|
14
|
+
from bencher.utils import int_to_col
|
15
|
+
from bencher.results.composable_container.composable_container_video import ComposableContainerVideo
|
14
16
|
|
15
17
|
|
16
18
|
class VideoSummaryResult(BenchResultBase):
|
@@ -19,6 +21,7 @@ class VideoSummaryResult(BenchResultBase):
|
|
19
21
|
result_var: Parameter = None,
|
20
22
|
input_order: List[str] = None,
|
21
23
|
reverse: bool = True,
|
24
|
+
result_types=(ResultImage,),
|
22
25
|
**kwargs,
|
23
26
|
) -> Optional[pn.panel]:
|
24
27
|
plot_filter = PlotFilter(
|
@@ -35,7 +38,7 @@ class VideoSummaryResult(BenchResultBase):
|
|
35
38
|
ds = self.to_dataset(ReduceType.SQUEEZE)
|
36
39
|
row = pn.Row()
|
37
40
|
for rv in self.get_results_var_list(result_var):
|
38
|
-
if isinstance(rv,
|
41
|
+
if isinstance(rv, result_types):
|
39
42
|
row.append(self.to_video_summary_ds(ds, rv, input_order, reverse, **kwargs))
|
40
43
|
return row
|
41
44
|
return matches_res.to_panel()
|
@@ -69,9 +72,127 @@ class VideoSummaryResult(BenchResultBase):
|
|
69
72
|
if isinstance(index[i], (int, float)):
|
70
73
|
index[i] = FormatFloat()(index[i])
|
71
74
|
label = ", ".join(f"{a[0]}={a[1]}" for a in list(zip(input_order, index)))
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
if val is not None:
|
76
|
+
vr.append_file(val, label)
|
77
|
+
fn = vr.write_png()
|
78
|
+
if fn is not None:
|
79
|
+
if video_controls is None:
|
80
|
+
video_controls = VideoControls()
|
81
|
+
vid = video_controls.video_container(fn, **kwargs)
|
82
|
+
return vid
|
83
|
+
|
84
|
+
return None
|
85
|
+
|
86
|
+
def to_video_grid(
|
87
|
+
self,
|
88
|
+
result_var: Parameter = None,
|
89
|
+
result_types=(ResultImage,),
|
90
|
+
**kwargs,
|
91
|
+
) -> Optional[pn.panel]:
|
92
|
+
plot_filter = PlotFilter(
|
93
|
+
float_range=VarRange(0, None),
|
94
|
+
cat_range=VarRange(0, None),
|
95
|
+
panel_range=VarRange(1, None),
|
96
|
+
input_range=VarRange(1, None),
|
97
|
+
)
|
98
|
+
matches_res = plot_filter.matches_result(
|
99
|
+
self.plt_cnt_cfg, callable_name(self.to_video_grid_ds)
|
100
|
+
)
|
101
|
+
if matches_res.overall:
|
102
|
+
ds = self.to_dataset(ReduceType.SQUEEZE)
|
103
|
+
row = pn.Row()
|
104
|
+
for rv in self.get_results_var_list(result_var):
|
105
|
+
if isinstance(rv, result_types):
|
106
|
+
row.append(self.to_video_grid_ds(ds, rv, **kwargs))
|
107
|
+
return row
|
108
|
+
return matches_res.to_panel()
|
109
|
+
|
110
|
+
def to_video_grid_ds(
|
111
|
+
self,
|
112
|
+
dataset: xr.Dataset,
|
113
|
+
result_var: Parameter,
|
114
|
+
reverse=True,
|
115
|
+
video_controls: VideoControls = None,
|
116
|
+
**kwargs,
|
117
|
+
):
|
118
|
+
vr = VideoWriter()
|
119
|
+
|
120
|
+
cvc = self._to_video_panes_ds(
|
121
|
+
dataset,
|
122
|
+
self.plot_cb,
|
123
|
+
target_dimension=0,
|
124
|
+
horizontal=True,
|
125
|
+
result_var=result_var,
|
126
|
+
final=True,
|
127
|
+
reverse=reverse,
|
128
|
+
**kwargs,
|
129
|
+
)
|
130
|
+
|
131
|
+
fn = vr.write_video_raw(cvc)
|
132
|
+
|
133
|
+
if fn is not None:
|
134
|
+
if video_controls is None:
|
135
|
+
video_controls = VideoControls()
|
136
|
+
vid = video_controls.video_container(fn, **kwargs)
|
137
|
+
return vid
|
138
|
+
return None
|
139
|
+
|
140
|
+
def plot_cb(self, dataset, result_var, **kwargs):
|
141
|
+
val = self.ds_to_container(dataset, result_var, container=None, **kwargs)
|
142
|
+
# print(val)
|
143
|
+
return val
|
144
|
+
|
145
|
+
def _to_video_panes_ds(
|
146
|
+
self,
|
147
|
+
dataset: xr.Dataset,
|
148
|
+
plot_callback: callable = None,
|
149
|
+
target_dimension=0,
|
150
|
+
horizontal=False,
|
151
|
+
result_var=None,
|
152
|
+
final=False,
|
153
|
+
reverse=False,
|
154
|
+
**kwargs,
|
155
|
+
) -> pn.panel:
|
156
|
+
num_dims = len(dataset.sizes)
|
157
|
+
dims = list(d for d in dataset.sizes)
|
158
|
+
if reverse:
|
159
|
+
dims = list(reversed(dims))
|
160
|
+
|
161
|
+
if num_dims > (target_dimension) and num_dims != 0:
|
162
|
+
selected_dim = dims[-1]
|
163
|
+
# print(f"selected dim {selected_dim}")
|
164
|
+
dim_color = int_to_col(num_dims - 2, 0.05, 1.0)
|
165
|
+
|
166
|
+
outer_container = ComposableContainerVideo(
|
167
|
+
name=" vs ".join(dims),
|
168
|
+
background_col=dim_color,
|
169
|
+
horizontal=horizontal,
|
170
|
+
# var_name=selected_dim,
|
171
|
+
# var_value=label_val,
|
172
|
+
)
|
173
|
+
max_len = 0
|
174
|
+
for i in range(dataset.sizes[selected_dim]):
|
175
|
+
sliced = dataset.isel({selected_dim: i})
|
176
|
+
label_val = sliced.coords[selected_dim].values.item()
|
177
|
+
inner_container = ComposableContainerVideo(
|
178
|
+
outer_container.name,
|
179
|
+
var_name=selected_dim,
|
180
|
+
var_value=label_val,
|
181
|
+
horizontal=horizontal,
|
182
|
+
)
|
183
|
+
panes = self._to_video_panes_ds(
|
184
|
+
sliced,
|
185
|
+
plot_callback=plot_callback,
|
186
|
+
target_dimension=target_dimension,
|
187
|
+
horizontal=len(sliced.sizes) <= target_dimension + 1,
|
188
|
+
result_var=result_var,
|
189
|
+
)
|
190
|
+
inner_container.append(panes)
|
191
|
+
|
192
|
+
if inner_container.label_len > max_len:
|
193
|
+
max_len = inner_container.label_len
|
194
|
+
|
195
|
+
rendered = inner_container.render()
|
196
|
+
outer_container.append(rendered)
|
197
|
+
return outer_container.render(concatenate=final)
|
198
|
+
return plot_callback(dataset=dataset, result_var=result_var, **kwargs)
|
bencher/utils.py
CHANGED
@@ -132,11 +132,11 @@ def gen_path(filename, folder, suffix):
|
|
132
132
|
return f"{path.absolute().as_posix()}/{filename}_{uuid4()}{suffix}"
|
133
133
|
|
134
134
|
|
135
|
-
def gen_video_path(video_name: str, extension: str = ".webm") -> str:
|
135
|
+
def gen_video_path(video_name: str = "vid", extension: str = ".webm") -> str:
|
136
136
|
return gen_path(video_name, "vid", extension)
|
137
137
|
|
138
138
|
|
139
|
-
def gen_image_path(image_name: str, filetype=".png") -> str:
|
139
|
+
def gen_image_path(image_name: str = "img", filetype=".png") -> str:
|
140
140
|
return gen_path(image_name, "img", filetype)
|
141
141
|
|
142
142
|
|
@@ -125,15 +125,32 @@ class ParametrizedSweep(Parameterized):
|
|
125
125
|
return item.name != p_name
|
126
126
|
|
127
127
|
@classmethod
|
128
|
-
def get_input_defaults(
|
128
|
+
def get_input_defaults(
|
129
|
+
cls,
|
130
|
+
override_defaults: List = None,
|
131
|
+
) -> List[Tuple[Parameter, Any]]:
|
129
132
|
inp = cls.get_inputs_only()
|
130
133
|
if override_defaults is None:
|
131
134
|
override_defaults = []
|
132
135
|
assert isinstance(override_defaults, list)
|
136
|
+
|
133
137
|
for p in override_defaults:
|
134
138
|
inp = filter(partial(ParametrizedSweep.filter_fn, p_name=p[0].name), inp)
|
139
|
+
|
135
140
|
return override_defaults + [[i, i.default] for i in inp]
|
136
141
|
|
142
|
+
@classmethod
|
143
|
+
def get_input_defaults_override(cls, **kwargs) -> dict[str, Any]:
|
144
|
+
inp = cls.get_inputs_only()
|
145
|
+
defaults = {}
|
146
|
+
for i in inp:
|
147
|
+
defaults[i.name] = i.default
|
148
|
+
|
149
|
+
for k, v in kwargs.items():
|
150
|
+
defaults[k] = v
|
151
|
+
|
152
|
+
return defaults
|
153
|
+
|
137
154
|
@classmethod
|
138
155
|
def get_results_only(cls) -> List[Parameter]:
|
139
156
|
"""Return a list of input parameters
|
bencher/video_writer.py
CHANGED
@@ -2,10 +2,16 @@ import numpy as np
|
|
2
2
|
import moviepy.video.io.ImageSequenceClip
|
3
3
|
from pathlib import Path
|
4
4
|
from .utils import gen_video_path, gen_image_path
|
5
|
+
|
5
6
|
import moviepy
|
6
|
-
from moviepy.editor import
|
7
|
+
from moviepy.editor import (
|
8
|
+
VideoFileClip,
|
9
|
+
ImageClip,
|
10
|
+
ImageSequenceClip,
|
11
|
+
clips_array,
|
12
|
+
concatenate_videoclips,
|
13
|
+
)
|
7
14
|
from PIL import Image, ImageDraw
|
8
|
-
from moviepy.editor import clips_array, concatenate_videoclips
|
9
15
|
|
10
16
|
|
11
17
|
class VideoWriter:
|
@@ -18,21 +24,26 @@ class VideoWriter:
|
|
18
24
|
def append(self, img):
|
19
25
|
self.images.append(img)
|
20
26
|
|
21
|
-
def write(self, bitrate: int =
|
22
|
-
# todo
|
23
|
-
# if len(self.images[0.shape) == 2:
|
24
|
-
# for i in range(len(self.images)):
|
25
|
-
# self.images[i] = np.expand_dims(self.images[i], 2)
|
26
|
-
|
27
|
+
def write(self, bitrate: int = 2000) -> str:
|
27
28
|
clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(
|
28
29
|
self.images, fps=30, with_mask=False, load_images=True
|
29
30
|
)
|
30
|
-
|
31
|
+
self.write_video_raw(clip, bitrate=bitrate)
|
31
32
|
return self.filename
|
32
33
|
|
33
|
-
|
34
|
+
@staticmethod
|
35
|
+
def create_label(label, width, height=20):
|
34
36
|
new_img = Image.new("RGB", (width, height), (255, 255, 255))
|
35
|
-
ImageDraw.Draw(new_img).text((
|
37
|
+
# ImageDraw.Draw(new_img).text((width/2, 0), label, (0, 0, 0),align="center",achor="ms")
|
38
|
+
ImageDraw.Draw(new_img).text((width / 2.0, 0), label, (0, 0, 0), anchor="mt")
|
39
|
+
|
40
|
+
return new_img
|
41
|
+
|
42
|
+
@staticmethod
|
43
|
+
def label_image(path: Path, label, padding=20) -> Path:
|
44
|
+
image = Image.open(path)
|
45
|
+
new_img = VideoWriter.create_label(label, image.size[0], image.size[1] + padding)
|
46
|
+
new_img.paste(image, (0, padding))
|
36
47
|
return new_img
|
37
48
|
|
38
49
|
def append_file(self, filepath, label=None):
|
@@ -66,21 +77,34 @@ class VideoWriter:
|
|
66
77
|
else:
|
67
78
|
self.image_files.append(filepath)
|
68
79
|
|
69
|
-
def
|
70
|
-
if
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
80
|
+
def to_images_sequence(self, images, target_duration: float = 10.0, frame_time=None):
|
81
|
+
if isinstance(images, list) and len(images) > 0:
|
82
|
+
if frame_time is None:
|
83
|
+
fps = len(images) / target_duration
|
84
|
+
fps = max(fps, 1) # never slower that 1 seconds per frame
|
85
|
+
fps = min(fps, 30)
|
86
|
+
else:
|
87
|
+
fps = 1.0 / frame_time
|
88
|
+
return ImageSequenceClip(images, fps=fps, with_mask=False)
|
89
|
+
return None
|
76
90
|
|
91
|
+
def write_png(self):
|
92
|
+
clip = None
|
77
93
|
if len(self.image_files) > 0:
|
78
|
-
clip =
|
79
|
-
clip.write_videofile(self.filename, bitrate=f"{bitrate}k", logger=None)
|
94
|
+
clip = self.to_images_sequence(self.image_files)
|
80
95
|
if len(self.video_files) > 0:
|
81
96
|
clip = concatenate_videoclips([VideoFileClip(f) for f in self.video_files])
|
82
|
-
|
83
|
-
|
97
|
+
if clip is not None:
|
98
|
+
clip.write_videofile(self.filename)
|
99
|
+
return self.filename
|
100
|
+
return None
|
101
|
+
|
102
|
+
def write_video_raw(
|
103
|
+
self, video_clip: moviepy.video.VideoClip, bitrate: int = 2000, fps: int = 30
|
104
|
+
) -> str:
|
105
|
+
video_clip.write_videofile(self.filename, codec="libvpx", bitrate=f"{bitrate}k", fps=fps)
|
106
|
+
video_clip.close()
|
107
|
+
return self.filename
|
84
108
|
|
85
109
|
|
86
110
|
def add_image(np_array: np.ndarray, name: str = "img"):
|
@@ -1,33 +1,33 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: holobench
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.7.0
|
4
4
|
Summary: A package for benchmarking the performance of arbitrary functions
|
5
5
|
Author-email: Austin Gregg-Smith <blooop@gmail.com>
|
6
6
|
Description-Content-Type: text/markdown
|
7
7
|
Requires-Dist: holoviews>=1.15,<=1.18.1
|
8
|
-
Requires-Dist: numpy>=1.0,<=1.26.
|
9
|
-
Requires-Dist: param>=1.13.0,<=2.0.
|
10
|
-
Requires-Dist: hvplot>=0.8,<=0.9.
|
8
|
+
Requires-Dist: numpy>=1.0,<=1.26.3
|
9
|
+
Requires-Dist: param>=1.13.0,<=2.0.2
|
10
|
+
Requires-Dist: hvplot>=0.8,<=0.9.2
|
11
11
|
Requires-Dist: matplotlib>=3.6.3,<=3.8.2
|
12
|
-
Requires-Dist: panel>=1.3.6,<=1.3.
|
12
|
+
Requires-Dist: panel>=1.3.6,<=1.3.8
|
13
13
|
Requires-Dist: diskcache>=5.6,<=5.6.3
|
14
14
|
Requires-Dist: optuna>=3.2,<=3.5.0
|
15
|
-
Requires-Dist: xarray>=2023.7,<=
|
15
|
+
Requires-Dist: xarray>=2023.7,<=2024.1.1
|
16
16
|
Requires-Dist: plotly>=5.15,<=5.18.0
|
17
17
|
Requires-Dist: sortedcontainers>=2.4,<=2.4
|
18
|
-
Requires-Dist: pandas>=2.0,<=2.
|
18
|
+
Requires-Dist: pandas>=2.0,<=2.2.0
|
19
19
|
Requires-Dist: strenum>=0.4.0,<=0.4.15
|
20
|
-
Requires-Dist: scikit-learn>=1.2,<=1.
|
20
|
+
Requires-Dist: scikit-learn>=1.2,<=1.4.0
|
21
21
|
Requires-Dist: str2bool>=1.1,<=1.1
|
22
22
|
Requires-Dist: scoop>=0.7.0,<=0.7.2.0
|
23
23
|
Requires-Dist: moviepy>=1.0.3,<=1.0.3
|
24
|
-
Requires-Dist: black>=23,<=
|
24
|
+
Requires-Dist: black>=23,<=24.1.1 ; extra == "test"
|
25
25
|
Requires-Dist: pylint>=2.16,<=3.0.3 ; extra == "test"
|
26
26
|
Requires-Dist: pytest-cov>=4.1,<=4.1 ; extra == "test"
|
27
|
-
Requires-Dist: pytest>=7.4,<=
|
28
|
-
Requires-Dist: hypothesis>=6.82,<=6.
|
29
|
-
Requires-Dist: ruff>=0.0.280,<=0.1.
|
30
|
-
Requires-Dist: coverage>=7.2.7,<=7.4.
|
27
|
+
Requires-Dist: pytest>=7.4,<=8.0.0 ; extra == "test"
|
28
|
+
Requires-Dist: hypothesis>=6.82,<=6.97.4 ; extra == "test"
|
29
|
+
Requires-Dist: ruff>=0.0.280,<=0.1.15 ; extra == "test"
|
30
|
+
Requires-Dist: coverage>=7.2.7,<=7.4.1 ; extra == "test"
|
31
31
|
Project-URL: Documentation, https://bencher.readthedocs.io/en/latest/
|
32
32
|
Project-URL: Home, https://github.com/dyson-ai/bencher
|
33
33
|
Project-URL: Repository, https://github.com/dyson-ai/bencher
|
@@ -1,14 +1,14 @@
|
|
1
1
|
bencher/__init__.py,sha256=lw9moEkY3rb3kQVS3_SM9L0LsOAXRSM1JUJ_mm16tMQ,1236
|
2
|
-
bencher/bench_cfg.py,sha256=
|
3
|
-
bencher/bench_plot_server.py,sha256=
|
4
|
-
bencher/bench_report.py,sha256=
|
5
|
-
bencher/bench_runner.py,sha256=
|
6
|
-
bencher/bencher.py,sha256=
|
2
|
+
bencher/bench_cfg.py,sha256=IUhAhjpn_RkBTHonWN7deye_gYLgvTHszSGtcHj4MJY,18867
|
3
|
+
bencher/bench_plot_server.py,sha256=ZePbN9lKMQggONYMBW0CJm9saLjmxtdeAEs6eiei_8g,4088
|
4
|
+
bencher/bench_report.py,sha256=jh3T_q9KByZDeMPMf0KNJojZukxRzkfaYGeuWQU8MKM,10528
|
5
|
+
bencher/bench_runner.py,sha256=AE1V0zGOxhe6qByFbb-V60GUugg3vQzyUxFK8Z1yc6Y,6072
|
6
|
+
bencher/bencher.py,sha256=GZBRPT3BsA-Iv9I0lRl8QgOJnAee8fnPa94vcyRvzxs,32438
|
7
7
|
bencher/caching.py,sha256=AusaNrzGGlj5m6zcwcqnTn55Mam2mQdF--oqelO806M,1627
|
8
8
|
bencher/job.py,sha256=Q2zpia95Ibukk8EeFq5IBbpo2PMRe7o5keelJCJlGnI,5927
|
9
9
|
bencher/optuna_conversions.py,sha256=9nLVPAydSQ8PyJlyhzs__Em6_Rx8l8Ld94UNJZxy6cY,5303
|
10
|
-
bencher/utils.py,sha256=
|
11
|
-
bencher/video_writer.py,sha256=
|
10
|
+
bencher/utils.py,sha256=QwK8uqt5RhYJc5A3zsHiuBxx7CHOa1YxwDNA6QBzCz4,5536
|
11
|
+
bencher/video_writer.py,sha256=TiNpx5MteVzZLf0jUfZoSQrhvvTCUTewRkHGod9IOnE,4286
|
12
12
|
bencher/worker_job.py,sha256=FREi0yWQACFmH86R1j-LH72tALEFkKhLDmmoGQY9Jh4,1571
|
13
13
|
bencher/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
bencher/example/benchmark_data.py,sha256=D9yUg_KKtqqEkAiLceodDwsv6sh7xEFWZNp6P6Y3pj4,6989
|
@@ -17,23 +17,23 @@ bencher/example/example_categorical.py,sha256=3BeOQN58nCGx6xzB0YvkgaBFInzJ5L3XsI
|
|
17
17
|
bencher/example/example_custom_sweep.py,sha256=-y8mYuXYD91j8kcCEe9c6Gx6g1dK-bvHM9sbXqHL2gA,1916
|
18
18
|
bencher/example/example_docs.py,sha256=aUi33O543JBPoOGlpHaY2eA74GR7cHH_6-hcC8xf3z0,1174
|
19
19
|
bencher/example/example_float3D.py,sha256=pwi3YlDad3NL4IrfMK2V5yV1CRpqfmUO-zUnGmVYxDs,3425
|
20
|
-
bencher/example/example_float_cat.py,sha256=
|
20
|
+
bencher/example/example_float_cat.py,sha256=7f1xL5Rjn2Iy5TaVrvTafFiPZZU-KHQ6TvW6HyVrCMo,3811
|
21
21
|
bencher/example/example_floats.py,sha256=HcQgfwldTVeFBmBTMtZ0yRy17ZJ4cfJeI_t8TxY2iOI,4269
|
22
22
|
bencher/example/example_floats2D.py,sha256=D0kljoUCinMKCEW-Zg-cQ8sYu_yPCZqzKJ9tRtt-Ono,3697
|
23
23
|
bencher/example/example_holosweep.py,sha256=d9g5aWCAUb7FMZahb4t3xNs344EshPhA-8-K6K1SBXg,3180
|
24
24
|
bencher/example/example_holosweep_objects.py,sha256=vHuAtkM1VrJelHOazn_SJfzxNywKyaMzN-DE8W7Ricc,3228
|
25
25
|
bencher/example/example_holosweep_tap.py,sha256=3ayQ0bTj_XWP_92ifQJAhe1whwPAj_xWHPkzC7fvqAY,4540
|
26
|
-
bencher/example/example_image.py,sha256=
|
26
|
+
bencher/example/example_image.py,sha256=87op1Z-VW-ypZOWjWUxDvJYJ9-MyELvb3_QW3sWkg5o,3410
|
27
27
|
bencher/example/example_levels.py,sha256=rpSNB571yfMnT7iO66Ds-DPGHWzOTM9FLMNfSetJdHY,6896
|
28
28
|
bencher/example/example_pareto.py,sha256=yyAg8Vb-5sgsS6LkYKT7T5Evcfg69FlCqCakUippSmU,2687
|
29
29
|
bencher/example/example_sample_cache.py,sha256=7gf1BJ63VAgdqNuNXkbL9-jeTeC3kXA_PY9yG3ulTz0,4200
|
30
30
|
bencher/example/example_sample_cache_context.py,sha256=IAUBbL78QM20R8evaq7L8I-xPxFDFykF1Gk1y2Ru1W0,4063
|
31
31
|
bencher/example/example_simple.py,sha256=Nn2ixNx29jbgvwH2K5vDGhSFcqKLMNaP1occPxhHoU0,11703
|
32
|
-
bencher/example/example_simple_bool.py,sha256=
|
33
|
-
bencher/example/example_simple_cat.py,sha256=
|
32
|
+
bencher/example/example_simple_bool.py,sha256=GZ6pyj8FaQV9gNxaqAmX6c5XWtMvKosezAbSADEl0G0,1248
|
33
|
+
bencher/example/example_simple_cat.py,sha256=XsV_75Jk3phVPI4om3q0vn1POfREb3CGRm9Kq1tL-OA,1760
|
34
34
|
bencher/example/example_simple_float.py,sha256=X4vsH0F4fZAoO0EKB1xIzFMY0f0Wyk8LV2plPlSEsbI,1323
|
35
35
|
bencher/example/example_strings.py,sha256=BdsEZgLT9mOxLkBKNHz2XpPwwe4SzNTdjnY1WVlOmNM,1570
|
36
|
-
bencher/example/example_time_event.py,sha256=
|
36
|
+
bencher/example/example_time_event.py,sha256=e6R-a6ZPe-ePiWoNvN3YuSQK-Y2HOGntsjCm_SPon28,2159
|
37
37
|
bencher/example/example_video.py,sha256=IJGgRHmpeppOKwhSkDHDJVuHw32yBJiBpbKaI34soHI,3742
|
38
38
|
bencher/example/example_workflow.py,sha256=00QnUuViMfX_PqzqkXmg1wPX6yAq7IS7mCL_RFKwrMM,6806
|
39
39
|
bencher/example/experimental/example_bokeh_plotly.py,sha256=3jUKh8eKIAlpklKnp8UopIHhUDw1A0_5CwjeyTzbi7o,846
|
@@ -57,20 +57,24 @@ bencher/plotting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
57
57
|
bencher/plotting/plot_filter.py,sha256=Zff02hEcRffiqDEoXUHVZQJK5kW4HbMxe2GYCrxI8jg,4688
|
58
58
|
bencher/plotting/plt_cnt_cfg.py,sha256=BkiAsgHm35Mqb5OsjULGVK0Q6pGZ0WSsJxxwSOrbaQs,3124
|
59
59
|
bencher/results/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
|
-
bencher/results/bench_result.py,sha256=
|
61
|
-
bencher/results/bench_result_base.py,sha256=
|
60
|
+
bencher/results/bench_result.py,sha256=9whs_FNftl3rfB7oGzwSbF_-kxRK5U2gvnwpvSRc9qQ,3366
|
61
|
+
bencher/results/bench_result_base.py,sha256=ymR99Fvnmbsw6Qn24wPP1gesofjATKwKX_FcpCT9ZWU,16257
|
62
62
|
bencher/results/float_formatter.py,sha256=sX6HNCyaXdHDxC8ybVUHwCJ3qOKbPUkBOplVIHtKWjM,1746
|
63
63
|
bencher/results/holoview_result.py,sha256=nfXYTaGQkXwLqJ_gEB3TYJxHfKAQCN1E60D9Tfbkxos,22254
|
64
64
|
bencher/results/optuna_result.py,sha256=jtsWJGdCS0L98EzxTxXU_AyarCL5CkXRLOVuSvs048M,13437
|
65
|
-
bencher/results/panel_result.py,sha256=
|
65
|
+
bencher/results/panel_result.py,sha256=ZHTkobAYHHsYYvQqLafba8g3rbT255hKO7u9A_oMBhM,1115
|
66
66
|
bencher/results/plotly_result.py,sha256=wkgfL38qJp6RviekXBYpNPeU4HCf0nbtKDAhu5QZhUg,2132
|
67
67
|
bencher/results/video_result.py,sha256=E3fAxXctRVxiRyamadpKCMXanM5TTqw1tEYICS2LDLs,1146
|
68
|
-
bencher/results/video_summary.py,sha256=
|
68
|
+
bencher/results/video_summary.py,sha256=2oYxPDjFknDg16OOm8hhr29_TyMNbBh3v8R2EuRoQQg,6875
|
69
|
+
bencher/results/composable_container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
70
|
+
bencher/results/composable_container/composable_container_base.py,sha256=u5bSXD0yuKSwvr2z3cWF1FijQqpr-pciPm86JxhLmJo,2323
|
71
|
+
bencher/results/composable_container/composable_container_panel.py,sha256=A56-MaaaId_VBMFhvM7Jtfh8_3PeKNxlEnOKU49IsW8,1221
|
72
|
+
bencher/results/composable_container/composable_container_video.py,sha256=yxvVTnK86O9kW2vIfSSv83zpSobVtFOB0-7tbcusbWA,1847
|
69
73
|
bencher/variables/inputs.py,sha256=XtUko3qNYB1xk7fwM9teVGRU0MNCW673n2teGtoyFGU,6393
|
70
|
-
bencher/variables/parametrised_sweep.py,sha256=
|
74
|
+
bencher/variables/parametrised_sweep.py,sha256=sqi5JPzloZ7f6fiZZwOFMa3AQnFWkfjYXQy7OWSask0,7361
|
71
75
|
bencher/variables/results.py,sha256=QCn7IZd4RwcRcDCp6DQp8w0wBMnHzluyw-Hu19Jg7Ig,6028
|
72
76
|
bencher/variables/sweep_base.py,sha256=I1LEeG1y5Jsw0a-Ik03t0tSzcfENht2GmBECJ3KNs28,6559
|
73
77
|
bencher/variables/time.py,sha256=Le7s8_oUYJD4wCqwQw-a_FRDpYQOi8CqMbGYsBF07jg,2860
|
74
|
-
holobench-1.
|
75
|
-
holobench-1.
|
76
|
-
holobench-1.
|
78
|
+
holobench-1.7.0.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
|
79
|
+
holobench-1.7.0.dist-info/METADATA,sha256=AL1TkmzE02fT5sl6xlhwdALAElXC8CeHyrir-8VF8oQ,5109
|
80
|
+
holobench-1.7.0.dist-info/RECORD,,
|
File without changes
|