holobench 1.6.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_report.py +1 -1
- bencher/bencher.py +16 -1
- bencher/example/example_image.py +18 -31
- bencher/results/bench_result.py +9 -1
- 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 +117 -0
- bencher/video_writer.py +43 -22
- {holobench-1.6.0.dist-info → holobench-1.7.0.dist-info}/METADATA +1 -1
- {holobench-1.6.0.dist-info → holobench-1.7.0.dist-info}/RECORD +16 -12
- {holobench-1.6.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_report.py
CHANGED
@@ -48,7 +48,7 @@ class BenchReport(BenchPlotServer):
|
|
48
48
|
self.pane.append(col)
|
49
49
|
|
50
50
|
def append_result(self, bench_res: BenchResult) -> None:
|
51
|
-
self.append_tab(bench_res.
|
51
|
+
self.append_tab(bench_res.plot(), bench_res.bench_cfg.title)
|
52
52
|
|
53
53
|
def append_tab(self, pane: pn.panel, name: str = None) -> None:
|
54
54
|
if pane is not None:
|
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
|
|
@@ -171,6 +172,10 @@ class Bench(BenchPlotServer):
|
|
171
172
|
self.input_vars = None
|
172
173
|
self.result_vars = None
|
173
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)
|
174
179
|
|
175
180
|
def set_worker(self, worker: Callable, worker_input_cfg: ParametrizedSweep = None) -> None:
|
176
181
|
"""Set the benchmark worker function and optionally the type the worker expects
|
@@ -243,6 +248,7 @@ class Bench(BenchPlotServer):
|
|
243
248
|
tag: str = "",
|
244
249
|
run_cfg: BenchRunCfg = None,
|
245
250
|
plot: bool = True,
|
251
|
+
plot_callbacks=None,
|
246
252
|
) -> BenchResult:
|
247
253
|
"""The all in 1 function benchmarker and results plotter.
|
248
254
|
|
@@ -257,7 +263,8 @@ class Bench(BenchPlotServer):
|
|
257
263
|
pass_repeat (bool,optional) By default do not pass the kwarg 'repeat' to the benchmark function. Set to true if
|
258
264
|
you want the benchmark function to be passed the repeat number
|
259
265
|
tag (str,optional): Use tags to group different benchmarks together.
|
260
|
-
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
|
261
268
|
Raises:
|
262
269
|
ValueError: If a result variable is not set
|
263
270
|
|
@@ -369,6 +376,12 @@ class Bench(BenchPlotServer):
|
|
369
376
|
"## Results Description\nPlease set post_description to explain these results"
|
370
377
|
)
|
371
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
|
+
|
372
385
|
bench_cfg = BenchCfg(
|
373
386
|
input_vars=input_vars,
|
374
387
|
result_vars=result_vars_only,
|
@@ -381,6 +394,7 @@ class Bench(BenchPlotServer):
|
|
381
394
|
pass_repeat=pass_repeat,
|
382
395
|
tag=run_cfg.run_tag + tag,
|
383
396
|
auto_plot=plot,
|
397
|
+
plot_callbacks=plot_callbacks,
|
384
398
|
)
|
385
399
|
return self.run_sweep(bench_cfg, run_cfg, time_src)
|
386
400
|
|
@@ -443,6 +457,7 @@ class Bench(BenchPlotServer):
|
|
443
457
|
|
444
458
|
if bench_cfg.auto_plot:
|
445
459
|
self.report.append_result(bench_res)
|
460
|
+
|
446
461
|
self.results.append(bench_res)
|
447
462
|
return bench_res
|
448
463
|
|
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
@@ -35,6 +35,14 @@ class BenchResult(PlotlyResult, HoloviewResult, VideoSummaryResult):
|
|
35
35
|
def plotly_callbacks():
|
36
36
|
return [HoloviewResult.to_surface, PlotlyResult.to_volume]
|
37
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
|
+
|
38
46
|
def to_auto(
|
39
47
|
self,
|
40
48
|
plot_list: List[callable] = None,
|
@@ -67,7 +75,7 @@ class BenchResult(PlotlyResult, HoloviewResult, VideoSummaryResult):
|
|
67
75
|
)
|
68
76
|
return row.pane
|
69
77
|
|
70
|
-
def to_auto_plots(self, **kwargs) ->
|
78
|
+
def to_auto_plots(self, **kwargs) -> pn.panel:
|
71
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
|
72
80
|
|
73
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
@@ -11,6 +11,8 @@ 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):
|
@@ -78,4 +80,119 @@ class VideoSummaryResult(BenchResultBase):
|
|
78
80
|
video_controls = VideoControls()
|
79
81
|
vid = video_controls.video_container(fn, **kwargs)
|
80
82
|
return vid
|
83
|
+
|
81
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/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,24 +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")
|
80
|
-
return self.filename
|
94
|
+
clip = self.to_images_sequence(self.image_files)
|
81
95
|
if len(self.video_files) > 0:
|
82
96
|
clip = concatenate_videoclips([VideoFileClip(f) for f in self.video_files])
|
97
|
+
if clip is not None:
|
83
98
|
clip.write_videofile(self.filename)
|
84
99
|
return self.filename
|
85
100
|
return None
|
86
|
-
|
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
|
87
108
|
|
88
109
|
|
89
110
|
def add_image(np_array: np.ndarray, name: str = "img"):
|
@@ -1,14 +1,14 @@
|
|
1
1
|
bencher/__init__.py,sha256=lw9moEkY3rb3kQVS3_SM9L0LsOAXRSM1JUJ_mm16tMQ,1236
|
2
|
-
bencher/bench_cfg.py,sha256=
|
2
|
+
bencher/bench_cfg.py,sha256=IUhAhjpn_RkBTHonWN7deye_gYLgvTHszSGtcHj4MJY,18867
|
3
3
|
bencher/bench_plot_server.py,sha256=ZePbN9lKMQggONYMBW0CJm9saLjmxtdeAEs6eiei_8g,4088
|
4
|
-
bencher/bench_report.py,sha256=
|
4
|
+
bencher/bench_report.py,sha256=jh3T_q9KByZDeMPMf0KNJojZukxRzkfaYGeuWQU8MKM,10528
|
5
5
|
bencher/bench_runner.py,sha256=AE1V0zGOxhe6qByFbb-V60GUugg3vQzyUxFK8Z1yc6Y,6072
|
6
|
-
bencher/bencher.py,sha256=
|
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
10
|
bencher/utils.py,sha256=QwK8uqt5RhYJc5A3zsHiuBxx7CHOa1YxwDNA6QBzCz4,5536
|
11
|
-
bencher/video_writer.py,sha256=
|
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
|
@@ -23,7 +23,7 @@ bencher/example/example_floats2D.py,sha256=D0kljoUCinMKCEW-Zg-cQ8sYu_yPCZqzKJ9tR
|
|
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
|
@@ -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
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
|