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 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
@@ -1,4 +1,5 @@
1
1
  """A server for display plots of benchmark results"""
2
+
2
3
  import logging
3
4
  import os
4
5
  from typing import List, Tuple
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 shutil
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.to_auto_plots(), bench_res.bench_cfg.title)
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
- directory = "tmpgit"
141
- report_path = self.save(directory, filename="index.html", in_html_folder=False)
142
- logging.info(f"created report at: {report_path.absolute()}")
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
- cd_dir = f"cd {directory} &&"
146
+ cd_dir = f"cd {directory} &&"
145
147
 
146
- os.system(f"{cd_dir} git init")
147
- os.system(f"{cd_dir} git checkout -b {branch_name}")
148
- os.system(f"{cd_dir} git add index.html")
149
- os.system(f'{cd_dir} git commit -m "publish {branch_name}"')
150
- os.system(f"{cd_dir} git remote add origin {remote}")
151
- os.system(f"{cd_dir} git push --set-upstream origin {branch_name} -f")
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 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
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 = self.worker_class_instance.get_inputs_only()
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 = self.worker_class_instance.get_results_only()
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 = self.worker_class_instance.get_input_defaults()
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 plot and bench_res.bench_cfg.auto_plot:
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)
@@ -1,4 +1,5 @@
1
1
  """Example of how to perform a parameter sweep for categorical variables"""
2
+
2
3
  import bencher as bch
3
4
 
4
5
 
@@ -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()
@@ -1,4 +1,5 @@
1
1
  """This file has some examples for how to perform basic benchmarking parameter sweeps"""
2
+
2
3
  import bencher as bch
3
4
 
4
5
  # All the examples will be using the data structures and benchmark function defined in this file
@@ -1,4 +1,5 @@
1
1
  """This file has some examples for how to perform basic benchmarking parameter sweeps"""
2
+
2
3
  # pylint: disable=duplicate-code
3
4
 
4
5
  import bencher as bch
@@ -1,4 +1,5 @@
1
1
  """This file has some examples for how to perform basic benchmarking parameter sweeps"""
2
+
2
3
  # pylint: disable=duplicate-code
3
4
 
4
5
  import bencher as bch
@@ -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) -> List[pn.panel]:
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
- 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):
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)
@@ -1,12 +1,10 @@
1
- from typing import Optional, Any
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]:
@@ -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, ResultVideo
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, (ResultImage, ResultVideo)):
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
- vr.append_file(val, label)
73
- vr.write_png()
74
- if video_controls is None:
75
- video_controls = VideoControls()
76
- vid = video_controls.video_container(vr.filename, **kwargs)
77
- return vid
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(cls, override_defaults: List = None) -> List[Tuple[Parameter, Any]]:
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 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,21 +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", 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
- clip.write_videofile(self.filename, logger=None)
83
- # clip = ImageSequenceClip(self.image_files, fps=fps, with_mask=False)
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.5.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
7
7
  Requires-Dist: holoviews>=1.15,<=1.18.1
8
- Requires-Dist: numpy>=1.0,<=1.26.2
9
- Requires-Dist: param>=1.13.0,<=2.0.1
10
- Requires-Dist: hvplot>=0.8,<=0.9.1
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.6
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,<=2023.12.0
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.1.4
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.3.2
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,<=23.12.1 ; extra == "test"
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,<=7.4.4 ; extra == "test"
28
- Requires-Dist: hypothesis>=6.82,<=6.92.2 ; extra == "test"
29
- Requires-Dist: ruff>=0.0.280,<=0.1.9 ; extra == "test"
30
- Requires-Dist: coverage>=7.2.7,<=7.4.0 ; extra == "test"
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=0dIF_JrvWYiugNDi28wh_8a8ejm99cnw3RrlMIDI2Ww,18714
3
- bencher/bench_plot_server.py,sha256=HnW6XpmARweMCd-koqzu1lxuj0gA4_fP-72D3Yfy-0M,4087
4
- bencher/bench_report.py,sha256=b9jLksrXzBhSmvI7KetO1i0hsHknE7qk4b4k0MGGtNw,10484
5
- bencher/bench_runner.py,sha256=TLJGn-NO4BBBZMG8W5E6u7IPyij67Yk3S4B29ONClnk,6049
6
- bencher/bencher.py,sha256=R9bvH_97T2qY7atwT38r2lDFzXSxr4ODfLxFGGVQNfM,31884
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=d8kkFpXXUF4d-Khdggdma95c5iT8fj-ZmHnE_mBpUqs,5520
11
- bencher/video_writer.py,sha256=1BVebcaIuUO6gJ-XMKSdFmhz5xbdAmhHNXIN084urDE,3725
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=RZiKEACZyGDnubxhaUcabI43AYvm3h7qt0SCUp5Yuso,3810
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=bsV2OYP2e_e8iXza-Xv94EAIG-W6L-hwJ047athOvoQ,3792
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=36KMSHyXZhzS1cp2TZnDLn7-GpShLdQ7mycuT0M3le8,1247
33
- bencher/example/example_simple_cat.py,sha256=YFccE84g37U2M3ByWYIcGNLXWdW_ktJbbZvGL0D6oHA,1759
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=y1vpK7UDrObEu0Z1x3e4OQzvGCQ7pF5GZvpKLegMbYk,2158
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=gKhMTCZ1V-hrkqwFRSE7c0Hswa7lD87RAi8gWzq32C4,3088
61
- bencher/results/bench_result_base.py,sha256=ZDm2DDngsg0QRglbaQlSMRpuxGHpoiiruAMGL1VuXso,15770
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=wJAIrmOocXlRzsYRIuA7G4BQSvcvNkq_LIYtKyrqZ2w,2175
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=r6C8y5sehoXpMHKutyVhYBFPPzvN7UZMNAMwm7fhEnM,2780
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=5yCsyzpyrWUfZSCNxnLPSAAay9YntCRb-UMoXTmsBOc,7037
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.5.0.dist-info/WHEEL,sha256=Sgu64hAMa6g5FdzHxXv9Xdse9yxpGGMeagVtPMWpJQY,99
75
- holobench-1.5.0.dist-info/METADATA,sha256=UkkQraH7uxRtV_qXQAepbLeY9sk7s6PMWGol96ahBvs,5110
76
- holobench-1.5.0.dist-info/RECORD,,
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,,