holobench 1.6.0__tar.gz → 1.7.0__tar.gz

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 (82) hide show
  1. {holobench-1.6.0 → holobench-1.7.0}/PKG-INFO +1 -1
  2. {holobench-1.6.0 → holobench-1.7.0}/bencher/bench_cfg.py +5 -0
  3. {holobench-1.6.0 → holobench-1.7.0}/bencher/bench_report.py +1 -1
  4. {holobench-1.6.0 → holobench-1.7.0}/bencher/bencher.py +16 -1
  5. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_image.py +18 -31
  6. {holobench-1.6.0 → holobench-1.7.0}/bencher/results/bench_result.py +9 -1
  7. {holobench-1.6.0 → holobench-1.7.0}/bencher/results/bench_result_base.py +55 -44
  8. holobench-1.7.0/bencher/results/composable_container/__init__.py +0 -0
  9. holobench-1.7.0/bencher/results/composable_container/composable_container_base.py +58 -0
  10. holobench-1.7.0/bencher/results/composable_container/composable_container_panel.py +40 -0
  11. holobench-1.7.0/bencher/results/composable_container/composable_container_video.py +54 -0
  12. holobench-1.7.0/bencher/results/panel_result.py +32 -0
  13. holobench-1.7.0/bencher/results/video_summary.py +198 -0
  14. {holobench-1.6.0 → holobench-1.7.0}/bencher/video_writer.py +43 -22
  15. {holobench-1.6.0 → holobench-1.7.0}/pyproject.toml +1 -1
  16. holobench-1.6.0/bencher/results/panel_result.py +0 -63
  17. holobench-1.6.0/bencher/results/video_summary.py +0 -81
  18. {holobench-1.6.0 → holobench-1.7.0}/README.md +0 -0
  19. {holobench-1.6.0 → holobench-1.7.0}/bencher/__init__.py +0 -0
  20. {holobench-1.6.0 → holobench-1.7.0}/bencher/bench_plot_server.py +0 -0
  21. {holobench-1.6.0 → holobench-1.7.0}/bencher/bench_runner.py +0 -0
  22. {holobench-1.6.0 → holobench-1.7.0}/bencher/caching.py +0 -0
  23. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/__init__.py +0 -0
  24. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/benchmark_data.py +0 -0
  25. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_all.py +0 -0
  26. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_categorical.py +0 -0
  27. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_custom_sweep.py +0 -0
  28. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_docs.py +0 -0
  29. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_float3D.py +0 -0
  30. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_float_cat.py +0 -0
  31. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_floats.py +0 -0
  32. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_floats2D.py +0 -0
  33. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_holosweep.py +0 -0
  34. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_holosweep_objects.py +0 -0
  35. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_holosweep_tap.py +0 -0
  36. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_levels.py +0 -0
  37. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_pareto.py +0 -0
  38. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_sample_cache.py +0 -0
  39. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_sample_cache_context.py +0 -0
  40. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_simple.py +0 -0
  41. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_simple_bool.py +0 -0
  42. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_simple_cat.py +0 -0
  43. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_simple_float.py +0 -0
  44. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_strings.py +0 -0
  45. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_time_event.py +0 -0
  46. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_video.py +0 -0
  47. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/example_workflow.py +0 -0
  48. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/experimental/example_bokeh_plotly.py +0 -0
  49. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/experimental/example_hover_ex.py +0 -0
  50. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/experimental/example_hvplot_explorer.py +0 -0
  51. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/experimental/example_interactive.py +0 -0
  52. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/experimental/example_streamnd.py +0 -0
  53. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/experimental/example_streams.py +0 -0
  54. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/experimental/example_template.py +0 -0
  55. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/experimental/example_updates.py +0 -0
  56. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/experimental/example_vector.py +0 -0
  57. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/meta/example_meta.py +0 -0
  58. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/meta/example_meta_cat.py +0 -0
  59. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/meta/example_meta_float.py +0 -0
  60. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/meta/example_meta_levels.py +0 -0
  61. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/optuna/example_optuna.py +0 -0
  62. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/shelved/example_float2D_scatter.py +0 -0
  63. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/shelved/example_float3D_cone.py +0 -0
  64. {holobench-1.6.0 → holobench-1.7.0}/bencher/example/shelved/example_kwargs.py +0 -0
  65. {holobench-1.6.0 → holobench-1.7.0}/bencher/job.py +0 -0
  66. {holobench-1.6.0 → holobench-1.7.0}/bencher/optuna_conversions.py +0 -0
  67. {holobench-1.6.0 → holobench-1.7.0}/bencher/plotting/__init__.py +0 -0
  68. {holobench-1.6.0 → holobench-1.7.0}/bencher/plotting/plot_filter.py +0 -0
  69. {holobench-1.6.0 → holobench-1.7.0}/bencher/plotting/plt_cnt_cfg.py +0 -0
  70. {holobench-1.6.0 → holobench-1.7.0}/bencher/results/__init__.py +0 -0
  71. {holobench-1.6.0 → holobench-1.7.0}/bencher/results/float_formatter.py +0 -0
  72. {holobench-1.6.0 → holobench-1.7.0}/bencher/results/holoview_result.py +0 -0
  73. {holobench-1.6.0 → holobench-1.7.0}/bencher/results/optuna_result.py +0 -0
  74. {holobench-1.6.0 → holobench-1.7.0}/bencher/results/plotly_result.py +0 -0
  75. {holobench-1.6.0 → holobench-1.7.0}/bencher/results/video_result.py +0 -0
  76. {holobench-1.6.0 → holobench-1.7.0}/bencher/utils.py +0 -0
  77. {holobench-1.6.0 → holobench-1.7.0}/bencher/variables/inputs.py +0 -0
  78. {holobench-1.6.0 → holobench-1.7.0}/bencher/variables/parametrised_sweep.py +0 -0
  79. {holobench-1.6.0 → holobench-1.7.0}/bencher/variables/results.py +0 -0
  80. {holobench-1.6.0 → holobench-1.7.0}/bencher/variables/sweep_base.py +0 -0
  81. {holobench-1.6.0 → holobench-1.7.0}/bencher/variables/time.py +0 -0
  82. {holobench-1.6.0 → holobench-1.7.0}/bencher/worker_job.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: holobench
3
- Version: 1.6.0
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
@@ -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
@@ -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.to_auto_plots(), bench_res.bench_cfg.title)
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:
@@ -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 and plotted
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
 
@@ -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=50)
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
- for s in [
58
- [BenchPolygons.param.sides],
59
- [BenchPolygons.param.sides, BenchPolygons.param.linewidth],
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
- if __name__ == "__main__":
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
- def example_image_vid(
87
- run_cfg: bch.BenchRunCfg = bch.BenchRunCfg(), report: bch.BenchReport = bch.BenchReport()
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
- res_list = bench.sweep_sequential(
98
- input_vars=["radius", "sides", "linewidth", "color"], group_size=3
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()
@@ -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) -> List[pn.panel]:
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
- dim_sel = dims[-1]
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
- background_col = dim_color
342
- name = " vs ".join(dims)
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
- for i in range(dataset.sizes[dim_sel]):
352
- sliced = dataset.isel({dim_sel: i})
353
-
354
- lable_val = sliced.coords[dim_sel].values.item()
355
- if isinstance(lable_val, (int, float)):
356
- lable_val = FormatFloat()(lable_val)
357
-
358
- label = f"{dim_sel}={lable_val}"
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
- width = num_dims - target_dimension
368
-
369
- container_args = {
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
- # outer_container.append(pn.Row(inner_container, align="center"))
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):
@@ -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)
@@ -0,0 +1,32 @@
1
+ from typing import Optional
2
+ from functools import partial
3
+ import panel as pn
4
+ from param import Parameter
5
+ from bencher.results.bench_result_base import BenchResultBase, ReduceType
6
+ from bencher.results.video_result import VideoControls
7
+ from bencher.variables.results import (
8
+ PANEL_TYPES,
9
+ )
10
+
11
+
12
+ class PanelResult(BenchResultBase):
13
+ def to_video(self, result_var: Parameter = None, **kwargs):
14
+ vc = VideoControls()
15
+ return pn.Column(
16
+ vc.video_controls(),
17
+ self.to_panes(result_var=result_var, container=vc.video_container, **kwargs),
18
+ )
19
+
20
+ def to_panes(
21
+ self, result_var: Parameter = None, target_dimension: int = 0, container=None, **kwargs
22
+ ) -> Optional[pn.pane.panel]:
23
+ if container is None:
24
+ container = pn.pane.panel
25
+ return self.map_plot_panes(
26
+ partial(self.ds_to_container, container=container),
27
+ hv_dataset=self.to_hv_dataset(ReduceType.SQUEEZE),
28
+ target_dimension=target_dimension,
29
+ result_var=result_var,
30
+ result_types=PANEL_TYPES,
31
+ **kwargs,
32
+ )
@@ -0,0 +1,198 @@
1
+ from bencher.utils import params_to_str
2
+ from typing import Optional, List
3
+ import itertools
4
+ import panel as pn
5
+ import xarray as xr
6
+ from param import Parameter
7
+ from bencher.results.bench_result_base import BenchResultBase, ReduceType
8
+ from bencher.variables.results import ResultImage
9
+ from bencher.plotting.plot_filter import VarRange, PlotFilter
10
+ from bencher.utils import callable_name, listify
11
+ from bencher.video_writer import VideoWriter
12
+ from bencher.results.float_formatter import FormatFloat
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
16
+
17
+
18
+ class VideoSummaryResult(BenchResultBase):
19
+ def to_video_summary(
20
+ self,
21
+ result_var: Parameter = None,
22
+ input_order: List[str] = None,
23
+ reverse: bool = True,
24
+ result_types=(ResultImage,),
25
+ **kwargs,
26
+ ) -> Optional[pn.panel]:
27
+ plot_filter = PlotFilter(
28
+ float_range=VarRange(0, None),
29
+ cat_range=VarRange(0, None),
30
+ panel_range=VarRange(1, None),
31
+ )
32
+ matches_res = plot_filter.matches_result(
33
+ self.plt_cnt_cfg, callable_name(self.to_video_summary_ds)
34
+ )
35
+
36
+ # video_controls = VideoControls()
37
+ if matches_res.overall:
38
+ ds = self.to_dataset(ReduceType.SQUEEZE)
39
+ row = pn.Row()
40
+ for rv in self.get_results_var_list(result_var):
41
+ if isinstance(rv, result_types):
42
+ row.append(self.to_video_summary_ds(ds, rv, input_order, reverse, **kwargs))
43
+ return row
44
+ return matches_res.to_panel()
45
+
46
+ def to_video_summary_ds(
47
+ self,
48
+ dataset: xr.Dataset,
49
+ result_var: Parameter,
50
+ input_order: List[str] = None,
51
+ reverse: bool = True,
52
+ video_controls: VideoControls = None,
53
+ **kwargs,
54
+ ):
55
+ vr = VideoWriter()
56
+ da = dataset[result_var.name]
57
+
58
+ if input_order is None:
59
+ input_order = list(da.dims)
60
+ else:
61
+ input_order = params_to_str(input_order)
62
+ if reverse:
63
+ input_order = list(reversed(input_order))
64
+
65
+ inputs_produc = [da.coords[i].values for i in input_order]
66
+
67
+ for index in itertools.product(*inputs_produc):
68
+ lookup = dict(zip(input_order, index))
69
+ val = da.loc[lookup].item()
70
+ index = listify(index)
71
+ for i in range(len(index)):
72
+ if isinstance(index[i], (int, float)):
73
+ index[i] = FormatFloat()(index[i])
74
+ label = ", ".join(f"{a[0]}={a[1]}" for a in list(zip(input_order, index)))
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)
@@ -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 VideoFileClip, ImageClip, ImageSequenceClip
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 = 1500) -> str:
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
- clip.write_videofile(self.filename, bitrate=f"{bitrate}k", logger=None)
31
+ self.write_video_raw(clip, bitrate=bitrate)
31
32
  return self.filename
32
33
 
33
- def create_label(self, label, width, height=20):
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((0, 0), label, (0, 0, 0))
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 write_png(self, bitrate: int = 1500, target_duration: float = 10.0, frame_time=None):
70
- if frame_time is None:
71
- fps = len(self.image_files) / target_duration
72
- fps = max(fps, 1) # never slower that 1 seconds per frame
73
- fps = min(fps, 30)
74
- else:
75
- fps = 1.0 / frame_time
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 = ImageSequenceClip(self.image_files, fps=fps, with_mask=False)
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
- # clip = ImageSequenceClip(self.image_files, fps=fps, with_mask=False)
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "holobench"
3
- version = "1.6.0"
3
+ version = "1.7.0"
4
4
 
5
5
  authors = [{ name = "Austin Gregg-Smith", email = "blooop@gmail.com" }]
6
6
  description = "A package for benchmarking the performance of arbitrary functions"
@@ -1,63 +0,0 @@
1
- from typing import Optional, Any
2
- from functools import partial
3
- import panel as pn
4
- import xarray as xr
5
- from param import Parameter
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
- ResultReference,
10
- PANEL_TYPES,
11
- )
12
-
13
-
14
- class PanelResult(BenchResultBase):
15
- def to_video(self, result_var: Parameter = None, **kwargs):
16
- vc = VideoControls()
17
- return pn.Column(
18
- vc.video_controls(),
19
- self.to_panes(result_var=result_var, container=vc.video_container, **kwargs),
20
- )
21
-
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
- def to_panes(
52
- self, result_var: Parameter = None, target_dimension: int = 0, container=None, **kwargs
53
- ) -> Optional[pn.pane.panel]:
54
- if container is None:
55
- container = pn.pane.panel
56
- return self.map_plot_panes(
57
- partial(self.ds_to_container, container=container),
58
- hv_dataset=self.to_hv_dataset(ReduceType.SQUEEZE),
59
- target_dimension=target_dimension,
60
- result_var=result_var,
61
- result_types=PANEL_TYPES,
62
- **kwargs,
63
- )
@@ -1,81 +0,0 @@
1
- from bencher.utils import params_to_str
2
- from typing import Optional, List
3
- import itertools
4
- import panel as pn
5
- import xarray as xr
6
- from param import Parameter
7
- from bencher.results.bench_result_base import BenchResultBase, ReduceType
8
- from bencher.variables.results import ResultImage
9
- from bencher.plotting.plot_filter import VarRange, PlotFilter
10
- from bencher.utils import callable_name, listify
11
- from bencher.video_writer import VideoWriter
12
- from bencher.results.float_formatter import FormatFloat
13
- from bencher.results.video_result import VideoControls
14
-
15
-
16
- class VideoSummaryResult(BenchResultBase):
17
- def to_video_summary(
18
- self,
19
- result_var: Parameter = None,
20
- input_order: List[str] = None,
21
- reverse: bool = True,
22
- result_types=(ResultImage,),
23
- **kwargs,
24
- ) -> Optional[pn.panel]:
25
- plot_filter = PlotFilter(
26
- float_range=VarRange(0, None),
27
- cat_range=VarRange(0, None),
28
- panel_range=VarRange(1, None),
29
- )
30
- matches_res = plot_filter.matches_result(
31
- self.plt_cnt_cfg, callable_name(self.to_video_summary_ds)
32
- )
33
-
34
- # video_controls = VideoControls()
35
- if matches_res.overall:
36
- ds = self.to_dataset(ReduceType.SQUEEZE)
37
- row = pn.Row()
38
- for rv in self.get_results_var_list(result_var):
39
- if isinstance(rv, result_types):
40
- row.append(self.to_video_summary_ds(ds, rv, input_order, reverse, **kwargs))
41
- return row
42
- return matches_res.to_panel()
43
-
44
- def to_video_summary_ds(
45
- self,
46
- dataset: xr.Dataset,
47
- result_var: Parameter,
48
- input_order: List[str] = None,
49
- reverse: bool = True,
50
- video_controls: VideoControls = None,
51
- **kwargs,
52
- ):
53
- vr = VideoWriter()
54
- da = dataset[result_var.name]
55
-
56
- if input_order is None:
57
- input_order = list(da.dims)
58
- else:
59
- input_order = params_to_str(input_order)
60
- if reverse:
61
- input_order = list(reversed(input_order))
62
-
63
- inputs_produc = [da.coords[i].values for i in input_order]
64
-
65
- for index in itertools.product(*inputs_produc):
66
- lookup = dict(zip(input_order, index))
67
- val = da.loc[lookup].item()
68
- index = listify(index)
69
- for i in range(len(index)):
70
- if isinstance(index[i], (int, float)):
71
- index[i] = FormatFloat()(index[i])
72
- label = ", ".join(f"{a[0]}={a[1]}" for a in list(zip(input_order, index)))
73
- if val is not None:
74
- vr.append_file(val, label)
75
- fn = vr.write_png()
76
- if fn is not None:
77
- if video_controls is None:
78
- video_controls = VideoControls()
79
- vid = video_controls.video_container(fn, **kwargs)
80
- return vid
81
- return None
File without changes
File without changes
File without changes
File without changes
File without changes