holobench 1.22.2__tar.gz → 1.24.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 (49) hide show
  1. {holobench-1.22.2/holobench.egg-info → holobench-1.24.0}/PKG-INFO +7 -5
  2. {holobench-1.22.2 → holobench-1.24.0}/bencher/__init__.py +9 -0
  3. {holobench-1.22.2 → holobench-1.24.0}/bencher/bench_runner.py +1 -1
  4. {holobench-1.22.2 → holobench-1.24.0}/bencher/bencher.py +22 -3
  5. {holobench-1.22.2 → holobench-1.24.0}/bencher/optuna_conversions.py +3 -2
  6. {holobench-1.22.2 → holobench-1.24.0}/bencher/utils.py +15 -2
  7. holobench-1.24.0/bencher/video_writer.py +67 -0
  8. {holobench-1.22.2 → holobench-1.24.0/holobench.egg-info}/PKG-INFO +7 -5
  9. {holobench-1.22.2 → holobench-1.24.0}/holobench.egg-info/SOURCES.txt +2 -0
  10. {holobench-1.22.2 → holobench-1.24.0}/holobench.egg-info/requires.txt +4 -4
  11. {holobench-1.22.2 → holobench-1.24.0}/pyproject.toml +18 -9
  12. {holobench-1.22.2 → holobench-1.24.0}/setup.py +2 -5
  13. {holobench-1.22.2 → holobench-1.24.0}/test/test_bench_examples.py +4 -0
  14. holobench-1.24.0/test/test_composable_container_base.py +77 -0
  15. holobench-1.24.0/test/test_composable_container_video.py +255 -0
  16. {holobench-1.22.2 → holobench-1.24.0}/test/test_utils.py +4 -0
  17. holobench-1.22.2/bencher/video_writer.py +0 -121
  18. {holobench-1.22.2 → holobench-1.24.0}/LICENSE +0 -0
  19. {holobench-1.22.2 → holobench-1.24.0}/README.md +0 -0
  20. {holobench-1.22.2 → holobench-1.24.0}/bencher/bench_cfg.py +0 -0
  21. {holobench-1.22.2 → holobench-1.24.0}/bencher/bench_plot_server.py +0 -0
  22. {holobench-1.22.2 → holobench-1.24.0}/bencher/bench_report.py +0 -0
  23. {holobench-1.22.2 → holobench-1.24.0}/bencher/caching.py +0 -0
  24. {holobench-1.22.2 → holobench-1.24.0}/bencher/class_enum.py +0 -0
  25. {holobench-1.22.2 → holobench-1.24.0}/bencher/job.py +0 -0
  26. {holobench-1.22.2 → holobench-1.24.0}/bencher/worker_job.py +0 -0
  27. {holobench-1.22.2 → holobench-1.24.0}/holobench.egg-info/dependency_links.txt +0 -0
  28. {holobench-1.22.2 → holobench-1.24.0}/holobench.egg-info/not-zip-safe +0 -0
  29. {holobench-1.22.2 → holobench-1.24.0}/holobench.egg-info/top_level.txt +0 -0
  30. {holobench-1.22.2 → holobench-1.24.0}/package.xml +0 -0
  31. {holobench-1.22.2 → holobench-1.24.0}/resource/bencher +0 -0
  32. {holobench-1.22.2 → holobench-1.24.0}/setup.cfg +0 -0
  33. {holobench-1.22.2 → holobench-1.24.0}/test/test_bench_report.py +0 -0
  34. {holobench-1.22.2 → holobench-1.24.0}/test/test_bench_result_base.py +0 -0
  35. {holobench-1.22.2 → holobench-1.24.0}/test/test_bench_runner.py +0 -0
  36. {holobench-1.22.2 → holobench-1.24.0}/test/test_bencher.py +0 -0
  37. {holobench-1.22.2 → holobench-1.24.0}/test/test_cache.py +0 -0
  38. {holobench-1.22.2 → holobench-1.24.0}/test/test_class_enum.py +0 -0
  39. {holobench-1.22.2 → holobench-1.24.0}/test/test_combinations.py +0 -0
  40. {holobench-1.22.2 → holobench-1.24.0}/test/test_float_formatter.py +0 -0
  41. {holobench-1.22.2 → holobench-1.24.0}/test/test_job.py +0 -0
  42. {holobench-1.22.2 → holobench-1.24.0}/test/test_meta_tests.py +0 -0
  43. {holobench-1.22.2 → holobench-1.24.0}/test/test_parametrized_sweep.py +0 -0
  44. {holobench-1.22.2 → holobench-1.24.0}/test/test_plot_filter.py +0 -0
  45. {holobench-1.22.2 → holobench-1.24.0}/test/test_plot_server.py +0 -0
  46. {holobench-1.22.2 → holobench-1.24.0}/test/test_sample_cache.py +0 -0
  47. {holobench-1.22.2 → holobench-1.24.0}/test/test_sweep_base.py +0 -0
  48. {holobench-1.22.2 → holobench-1.24.0}/test/test_sweep_vars.py +0 -0
  49. {holobench-1.22.2 → holobench-1.24.0}/test/test_vars.py +0 -0
@@ -1,8 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: holobench
3
- Version: 1.22.2
3
+ Version: 1.24.0
4
4
  Summary: A package for benchmarking the performance of arbitrary functions
5
5
  Author-email: Austin Gregg-Smith <blooop@gmail.com>
6
+ Maintainer: austin.gregg-smith
7
+ Maintainer-email: austin.gregg-smith@dyson.com
6
8
  Project-URL: Repository, https://github.com/dyson-ai/bencher
7
9
  Project-URL: Home, https://github.com/dyson-ai/bencher
8
10
  Project-URL: Documentation, https://bencher.readthedocs.io/en/latest/
@@ -13,7 +15,7 @@ Requires-Dist: numpy<=1.26.4,>=1.0
13
15
  Requires-Dist: param<=2.1.0,>=1.13.0
14
16
  Requires-Dist: hvplot<=0.10.0,>=0.8
15
17
  Requires-Dist: matplotlib<=3.9.0,>=3.6.3
16
- Requires-Dist: panel<=1.4.3,>=1.3.6
18
+ Requires-Dist: panel<=1.4.4,>=1.3.6
17
19
  Requires-Dist: diskcache<=5.6.3,>=5.6
18
20
  Requires-Dist: optuna<=3.6.1,>=3.2
19
21
  Requires-Dist: xarray<=2024.5.0,>=2023.7
@@ -30,9 +32,9 @@ Requires-Dist: black<=24.4.2,>=23; extra == "test"
30
32
  Requires-Dist: pylint<=3.2.2,>=2.17.7; extra == "test"
31
33
  Requires-Dist: pytest-cov<=5.0.0,>=4.1; extra == "test"
32
34
  Requires-Dist: pytest<=8.2.1,>=7.4; extra == "test"
33
- Requires-Dist: hypothesis<=6.102.6,>=6.82; extra == "test"
34
- Requires-Dist: ruff<=0.4.5,>=0.3; extra == "test"
35
- Requires-Dist: coverage<=7.5.2,>=7.2.7; extra == "test"
35
+ Requires-Dist: hypothesis<=6.103.0,>=6.82; extra == "test"
36
+ Requires-Dist: ruff<=0.4.7,>=0.3; extra == "test"
37
+ Requires-Dist: coverage<=7.5.3,>=7.2.7; extra == "test"
36
38
 
37
39
  # Bencher
38
40
 
@@ -22,6 +22,15 @@ from .variables.results import (
22
22
  curve,
23
23
  )
24
24
 
25
+ from .results.composable_container.composable_container_base import (
26
+ ComposeType,
27
+ ComposableContainerBase,
28
+ )
29
+ from .results.composable_container.composable_container_video import (
30
+ ComposableContainerVideo,
31
+ RenderCfg,
32
+ )
33
+
25
34
  from .plotting.plot_filter import VarRange, PlotFilter
26
35
  from .utils import (
27
36
  hmap_canonical_input,
@@ -121,7 +121,7 @@ class BenchRunner:
121
121
  self.show_publish(report_level, show, publish, save, debug)
122
122
  return self.results
123
123
 
124
- def show_publish(self, report, show, publish, save, debug):
124
+ def show_publish(self, report: BenchReport, show: bool, publish: bool, save: bool, debug: bool):
125
125
  if save:
126
126
  report.save_index()
127
127
  if publish and self.publisher is not None:
@@ -220,9 +220,11 @@ class Bench(BenchPlotServer):
220
220
  relationship_cb=None,
221
221
  plot_callbacks: List | bool = None,
222
222
  ) -> List[BenchResult]:
223
- results = []
224
223
  if relationship_cb is None:
225
224
  relationship_cb = combinations
225
+ if input_vars is None:
226
+ input_vars = self.worker_class_instance.get_inputs_only()
227
+ results = []
226
228
  for it in range(iterations):
227
229
  for input_group in relationship_cb(input_vars, group_size):
228
230
  title_gen = title + "Sweeping " + " vs ".join(params_to_str(input_group))
@@ -313,8 +315,21 @@ class Bench(BenchPlotServer):
313
315
  else:
314
316
  const_vars = deepcopy(const_vars)
315
317
 
316
- for i in range(len(input_vars)):
317
- input_vars[i] = self.convert_vars_to_params(input_vars[i], "input")
318
+ if isinstance(input_vars, dict):
319
+ input_lists = []
320
+ for k, v in input_vars.items():
321
+ param_var = self.convert_vars_to_params(k, "input")
322
+ if isinstance(v, list):
323
+ assert len(v) > 0
324
+ param_var = param_var.with_sample_values(v)
325
+ else:
326
+ raise RuntimeError("Unsupported type")
327
+ input_lists.append(param_var)
328
+
329
+ input_vars = input_lists
330
+ else:
331
+ for i in range(len(input_vars)):
332
+ input_vars[i] = self.convert_vars_to_params(input_vars[i], "input")
318
333
  for i in range(len(result_vars)):
319
334
  result_vars[i] = self.convert_vars_to_params(result_vars[i], "result")
320
335
 
@@ -482,6 +497,10 @@ class Bench(BenchPlotServer):
482
497
  """
483
498
  if isinstance(variable, str):
484
499
  variable = self.worker_class_instance.param.objects(instance=False)[variable]
500
+ if isinstance(variable, tuple):
501
+ variable = self.worker_class_instance.param.objects(instance=False)[
502
+ variable[0]
503
+ ].with_sample_values(variable[1])
485
504
  if not isinstance(variable, param.Parameter):
486
505
  raise TypeError(
487
506
  f"You need to use {var_type}_vars =[{self.worker_input_cfg}.param.your_variable], instead of {var_type}_vars =[{self.worker_input_cfg}.your_variable]"
@@ -1,4 +1,5 @@
1
1
  from typing import List
2
+ import logging
2
3
 
3
4
  import optuna
4
5
  import panel as pn
@@ -158,8 +159,8 @@ def summarise_optuna_study(study: optuna.study.Study) -> pn.pane.panel:
158
159
  row.append(plot_param_importances(study))
159
160
  try:
160
161
  row.append(plot_pareto_front(study))
161
- except Exception:
162
- pass
162
+ except Exception as e: # pylint: disable=broad-except
163
+ logging.exception(e)
163
164
 
164
165
  row.append(
165
166
  pn.pane.Markdown(f"```\nBest value: {study.best_value}\nParams: {study.best_params}```")
@@ -8,8 +8,9 @@ from colorsys import hsv_to_rgb
8
8
  from pathlib import Path
9
9
  from uuid import uuid4
10
10
  from functools import partial
11
- from typing import Callable, Any, List
11
+ from typing import Callable, Any, List, Tuple
12
12
  import param
13
+ import numpy as np
13
14
 
14
15
 
15
16
  def hmap_canonical_input(dic: dict) -> tuple:
@@ -98,6 +99,10 @@ def un_camel(camel: str) -> str:
98
99
  return capitalise_words(re.sub("([a-z])([A-Z])", r"\g<1> \g<2>", camel.replace("_", " ")))
99
100
 
100
101
 
102
+ def mult_tuple(inp: Tuple[float], val: float) -> Tuple[float]:
103
+ return tuple(np.array(inp) * val)
104
+
105
+
101
106
  def tabs_in_markdown(regular_str: str, spaces: int = 2) -> str:
102
107
  """Given a string with tabs in the form \t convert the to &ensp; which is a double space in markdown
103
108
 
@@ -142,13 +147,21 @@ def color_tuple_to_css(color: tuple[float, float, float]) -> str:
142
147
  return f"rgb{(color[0] * 255, color[1] * 255, color[2] * 255)}"
143
148
 
144
149
 
150
+ def color_tuple_to_255(color: tuple[float, float, float]) -> tuple[float, float, float]:
151
+ return (
152
+ min(int(color[0] * 255), 255),
153
+ min(int(color[1] * 255), 255),
154
+ min(int(color[2] * 255), 255),
155
+ )
156
+
157
+
145
158
  def gen_path(filename, folder="generic", suffix=".dat"):
146
159
  path = Path(f"cachedir/{folder}/{filename}/")
147
160
  path.mkdir(parents=True, exist_ok=True)
148
161
  return f"{path.absolute().as_posix()}/{filename}_{uuid4()}{suffix}"
149
162
 
150
163
 
151
- def gen_video_path(video_name: str = "vid", extension: str = ".webm") -> str:
164
+ def gen_video_path(video_name: str = "vid", extension: str = ".mp4") -> str:
152
165
  return gen_path(video_name, "vid", extension)
153
166
 
154
167
 
@@ -0,0 +1,67 @@
1
+ import numpy as np
2
+ import moviepy.video.io.ImageSequenceClip
3
+ from pathlib import Path
4
+ from .utils import gen_video_path, gen_image_path
5
+
6
+ import moviepy
7
+ from PIL import Image, ImageDraw
8
+
9
+
10
+ class VideoWriter:
11
+ def __init__(self, filename: str = "vid") -> None:
12
+ self.images = []
13
+ self.image_files = []
14
+ self.video_files = []
15
+ self.filename = gen_video_path(filename)
16
+
17
+ def append(self, img):
18
+ self.images.append(img)
19
+
20
+ def write(self) -> str:
21
+ if len(self.images) > 0:
22
+ clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(
23
+ self.images, fps=30, with_mask=False, load_images=True
24
+ )
25
+ self.write_video_raw(clip)
26
+ return self.filename
27
+
28
+ @staticmethod
29
+ def create_label(label, width=None, height=16, color=(255, 255, 255)):
30
+ if width is None:
31
+ width = len(label) * 10
32
+ new_img = Image.new("RGB", (width, height), color=color)
33
+ # ImageDraw.Draw(new_img).text((width/2, 0), label, (0, 0, 0),align="center",achor="ms")
34
+ ImageDraw.Draw(new_img).text(
35
+ (width / 2.0, 0), label, (0, 0, 0), anchor="mt", font_size=height
36
+ )
37
+
38
+ return new_img
39
+
40
+ @staticmethod
41
+ def label_image(path: Path, label, padding=20, color=(255, 255, 255)) -> Path:
42
+ image = Image.open(path)
43
+ new_img = VideoWriter.create_label(
44
+ label, image.size[0], image.size[1] + padding, color=color
45
+ )
46
+ new_img.paste(image, (0, padding))
47
+ return new_img
48
+
49
+ def write_video_raw(self, video_clip: moviepy.video.VideoClip, fps: int = 30) -> str:
50
+ video_clip.write_videofile(
51
+ self.filename,
52
+ codec="libx264",
53
+ audio=False,
54
+ bitrate="0",
55
+ fps=fps,
56
+ ffmpeg_params=["-crf", "23"],
57
+ threads=8,
58
+ )
59
+ video_clip.close()
60
+ return self.filename
61
+
62
+
63
+ def add_image(np_array: np.ndarray, name: str = "img") -> str:
64
+ """Creates a file on disk from a numpy array and returns the created image path"""
65
+ filename = gen_image_path(name)
66
+ Image.fromarray(np_array).save(filename)
67
+ return filename
@@ -1,8 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: holobench
3
- Version: 1.22.2
3
+ Version: 1.24.0
4
4
  Summary: A package for benchmarking the performance of arbitrary functions
5
5
  Author-email: Austin Gregg-Smith <blooop@gmail.com>
6
+ Maintainer: austin.gregg-smith
7
+ Maintainer-email: austin.gregg-smith@dyson.com
6
8
  Project-URL: Repository, https://github.com/dyson-ai/bencher
7
9
  Project-URL: Home, https://github.com/dyson-ai/bencher
8
10
  Project-URL: Documentation, https://bencher.readthedocs.io/en/latest/
@@ -13,7 +15,7 @@ Requires-Dist: numpy<=1.26.4,>=1.0
13
15
  Requires-Dist: param<=2.1.0,>=1.13.0
14
16
  Requires-Dist: hvplot<=0.10.0,>=0.8
15
17
  Requires-Dist: matplotlib<=3.9.0,>=3.6.3
16
- Requires-Dist: panel<=1.4.3,>=1.3.6
18
+ Requires-Dist: panel<=1.4.4,>=1.3.6
17
19
  Requires-Dist: diskcache<=5.6.3,>=5.6
18
20
  Requires-Dist: optuna<=3.6.1,>=3.2
19
21
  Requires-Dist: xarray<=2024.5.0,>=2023.7
@@ -30,9 +32,9 @@ Requires-Dist: black<=24.4.2,>=23; extra == "test"
30
32
  Requires-Dist: pylint<=3.2.2,>=2.17.7; extra == "test"
31
33
  Requires-Dist: pytest-cov<=5.0.0,>=4.1; extra == "test"
32
34
  Requires-Dist: pytest<=8.2.1,>=7.4; extra == "test"
33
- Requires-Dist: hypothesis<=6.102.6,>=6.82; extra == "test"
34
- Requires-Dist: ruff<=0.4.5,>=0.3; extra == "test"
35
- Requires-Dist: coverage<=7.5.2,>=7.2.7; extra == "test"
35
+ Requires-Dist: hypothesis<=6.103.0,>=6.82; extra == "test"
36
+ Requires-Dist: ruff<=0.4.7,>=0.3; extra == "test"
37
+ Requires-Dist: coverage<=7.5.3,>=7.2.7; extra == "test"
36
38
 
37
39
  # Bencher
38
40
 
@@ -32,6 +32,8 @@ test/test_bencher.py
32
32
  test/test_cache.py
33
33
  test/test_class_enum.py
34
34
  test/test_combinations.py
35
+ test/test_composable_container_base.py
36
+ test/test_composable_container_video.py
35
37
  test/test_float_formatter.py
36
38
  test/test_job.py
37
39
  test/test_meta_tests.py
@@ -3,7 +3,7 @@ numpy<=1.26.4,>=1.0
3
3
  param<=2.1.0,>=1.13.0
4
4
  hvplot<=0.10.0,>=0.8
5
5
  matplotlib<=3.9.0,>=3.6.3
6
- panel<=1.4.3,>=1.3.6
6
+ panel<=1.4.4,>=1.3.6
7
7
  diskcache<=5.6.3,>=5.6
8
8
  optuna<=3.6.1,>=3.2
9
9
  xarray<=2024.5.0,>=2023.7
@@ -21,6 +21,6 @@ black<=24.4.2,>=23
21
21
  pylint<=3.2.2,>=2.17.7
22
22
  pytest-cov<=5.0.0,>=4.1
23
23
  pytest<=8.2.1,>=7.4
24
- hypothesis<=6.102.6,>=6.82
25
- ruff<=0.4.5,>=0.3
26
- coverage<=7.5.2,>=7.2.7
24
+ hypothesis<=6.103.0,>=6.82
25
+ ruff<=0.4.7,>=0.3
26
+ coverage<=7.5.3,>=7.2.7
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "holobench"
3
- version = "1.22.2"
3
+ version = "1.24.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"
@@ -12,7 +12,7 @@ dependencies = [
12
12
  "param>=1.13.0,<=2.1.0",
13
13
  "hvplot>=0.8,<=0.10.0",
14
14
  "matplotlib>=3.6.3,<=3.9.0",
15
- "panel>=1.3.6,<=1.4.3",
15
+ "panel>=1.3.6,<=1.4.4",
16
16
  "diskcache>=5.6,<=5.6.3",
17
17
  "optuna>=3.2,<=3.6.1",
18
18
  "xarray>=2023.7,<=2024.5.0",
@@ -48,9 +48,9 @@ test = [
48
48
  "pylint>=2.17.7,<=3.2.2",
49
49
  "pytest-cov>=4.1,<=5.0.0",
50
50
  "pytest>=7.4,<=8.2.1",
51
- "hypothesis>=6.82,<=6.102.6",
52
- "ruff>=0.3,<=0.4.5",
53
- "coverage>=7.2.7,<=7.5.2",
51
+ "hypothesis>=6.82,<=6.103.0",
52
+ "ruff>=0.3,<=0.4.7",
53
+ "coverage>=7.2.7,<=7.5.3",
54
54
  ]
55
55
 
56
56
  [project.urls]
@@ -72,20 +72,29 @@ py311 = ["py311", "test"]
72
72
  format = "black ."
73
73
  check-clean-workspace = "git diff --exit-code"
74
74
  ruff-lint = "ruff check . --fix"
75
- pylint = "pylint --version && echo 'running pylint...'&& pylint $(git ls-files '*.py')"
75
+ pylint = "pylint --version && echo 'running pylint...' && pylint $(git ls-files '*.py')"
76
76
  lint = { depends_on = ["ruff-lint", "pylint"] }
77
77
  style = { depends_on = ["format", "lint"] }
78
+ commit-format = "git commit -a -m'autoformat code'"
78
79
  test = "pytest"
79
80
  coverage = "coverage run -m pytest && coverage xml -o coverage.xml"
80
81
  coverage-report = "coverage report -m"
82
+ update-lock = "pixi update && git commit -a -m'update pixi.lock'"
83
+ fix = { depends_on = ["update-lock", "format", "ruff-lint"] }
84
+ push = "git push"
85
+ update-lock-push = { depends_on = ["update-lock", "push"] }
86
+ fix-commit-push = { depends_on = ["fix", "commit-format", "update-lock-push"] }
87
+
81
88
  ci-no-cover = { depends_on = ["style", "test"] }
82
89
  ci = { depends_on = [
83
- "style",
90
+ "format",
91
+ "ruff-lint",
84
92
  "check-clean-workspace",
93
+ "pylint",
85
94
  "coverage",
86
95
  "coverage-report",
87
96
  ] }
88
- update-lock-push = "pixi update; git commit -a -m'update pixi.lock';git push"
97
+ ci-push = { depends_on = ["format", "ruff-lint", "update-lock", "ci", "push"] }
89
98
  clear-pixi = "rm -rf .pixi pixi.lock"
90
99
 
91
100
 
@@ -97,7 +106,7 @@ extension-pkg-whitelist = ["numpy", "scipy"]
97
106
  jobs = 16 #detect number of cores
98
107
 
99
108
  [tool.pylint.'MESSAGES CONTROL']
100
- disable = "C,logging-fstring-interpolation,line-too-long,fixme,broad-exception-caught,missing-module-docstring,too-many-instance-attributes,too-few-public-methods,too-many-arguments,too-many-locals,too-many-branches,too-many-statements,use-dict-literal,cyclic-import,duplicate-code,too-many-public-methods,too-many-nested-blocks"
109
+ disable = "C,logging-fstring-interpolation,line-too-long,fixme,missing-module-docstring,too-many-instance-attributes,too-few-public-methods,too-many-arguments,too-many-locals,too-many-branches,too-many-statements,use-dict-literal,duplicate-code,too-many-public-methods,too-many-nested-blocks"
101
110
  enable = "no-else-return,consider-using-in"
102
111
 
103
112
  [tool.black]
@@ -4,17 +4,14 @@ package_name = "bencher"
4
4
 
5
5
  setup(
6
6
  name=package_name,
7
- # version="0.0.1",
8
7
  description="A library for benchmarking code and generating reports for analysis",
9
- # maintainer="austin.gregg-smith",
10
- # maintainer_email="austin.gregg-smith@dyson.com",
8
+ maintainer="austin.gregg-smith",
9
+ maintainer_email="austin.gregg-smith@dyson.com",
11
10
  packages=find_packages(exclude=["test.*", "test"]),
12
11
  data_files=[
13
12
  ("share/ament_index/resource_index/packages", ["resource/" + package_name]),
14
13
  ("share/" + package_name, ["package.xml"]),
15
14
  ],
16
- include_package_data=False,
17
- # install_requires=["setuptools"],
18
15
  zip_safe=False,
19
16
  test_suite="test",
20
17
  tests_require=["pytest"],
@@ -9,6 +9,7 @@ from bencher.example.example_time_event import run_example_time_event
9
9
  from bencher.example.example_float3D import example_floats3D
10
10
 
11
11
  from bencher.example.example_custom_sweep import example_custom_sweep
12
+ from bencher.example.example_custom_sweep2 import example_custom_sweep2
12
13
  from bencher.example.example_workflow import example_floats2D_workflow, example_floats3D_workflow
13
14
  from bencher.example.example_holosweep import example_holosweep
14
15
  from bencher.example.example_holosweep_tap import example_holosweep_tap
@@ -85,6 +86,9 @@ class TestBenchExamples(unittest.TestCase):
85
86
  def test_example_custom_sweep(self) -> None:
86
87
  self.examples_asserts(example_custom_sweep(self.create_run_cfg()))
87
88
 
89
+ def test_example_custom2(self) -> None:
90
+ self.examples_asserts(example_custom_sweep2(self.create_run_cfg()))
91
+
88
92
  def test_example_floats2D_workflow(self) -> None:
89
93
  self.examples_asserts(example_floats2D_workflow(self.create_run_cfg()))
90
94
 
@@ -0,0 +1,77 @@
1
+ # Generated by CodiumAI
2
+ from bencher.results.composable_container.composable_container_base import (
3
+ ComposeType,
4
+ ComposableContainerBase,
5
+ )
6
+
7
+
8
+ import pytest
9
+
10
+
11
+ class TestFlip:
12
+
13
+ # flipping ComposeType.right returns ComposeType.down
14
+ def test_flip_right_returns_down(self):
15
+ assert ComposeType.right.flip() == ComposeType.down
16
+
17
+ # flipping ComposeType.down returns ComposeType.right
18
+ def test_flip_down_returns_right(self):
19
+ assert ComposeType.down.flip() == ComposeType.right
20
+
21
+ # flipping an invalid ComposeType raises RuntimeError
22
+ def test_flip_invalid_raises_runtime_error(self):
23
+ with pytest.raises(RuntimeError):
24
+ invalid_type = ComposeType.sequence
25
+ invalid_type.flip()
26
+
27
+ # method should handle large number of flip calls without performance degradation
28
+ def test_flip_large_number_of_calls(self):
29
+ for _ in range(1000000):
30
+ assert ComposeType.right.flip() == ComposeType.down
31
+ assert ComposeType.down.flip() == ComposeType.right
32
+
33
+ # method should maintain immutability of the original ComposeType instance
34
+ def test_flip_maintains_immutability(self):
35
+ original = ComposeType.right
36
+ flipped = original.flip()
37
+ assert original == ComposeType.right
38
+ assert flipped == ComposeType.down
39
+
40
+
41
+ # Generated by CodiumAI
42
+ class TestComposableContainerBase:
43
+
44
+ # append method adds an object to the container
45
+ def test_append_adds_object(self):
46
+ container = ComposableContainerBase()
47
+ obj = "test_object"
48
+ container.append(obj)
49
+ assert obj in container.container
50
+
51
+ # render method returns the current state of the container
52
+ def test_render_returns_container_state(self):
53
+ container = ComposableContainerBase()
54
+ obj = "test_object"
55
+ container.append(obj)
56
+ assert container.render() == [obj]
57
+
58
+ # Ensure label_formatter formats int values correctly with spaces included
59
+ def test_label_formatter_formats_int_with_spaces(self):
60
+ formatted_label = ComposableContainerBase.label_formatter("var", 12345)
61
+ assert formatted_label == "var= 12345."
62
+
63
+ # label_formatter handles None values for var_name and var_value
64
+ def test_label_formatter_handles_none(self):
65
+ formatted_label = ComposableContainerBase.label_formatter(None, None)
66
+ assert formatted_label is None
67
+
68
+ # append method handles appending None
69
+ def test_append_handles_none(self):
70
+ container = ComposableContainerBase()
71
+ container.append(None)
72
+ assert None in container.container
73
+
74
+ # render method handles an empty container
75
+ def test_render_empty_container(self):
76
+ container = ComposableContainerBase()
77
+ assert container.render() == []
@@ -0,0 +1,255 @@
1
+ import unittest
2
+ import bencher as bch
3
+ import numpy as np
4
+ from hypothesis import given, strategies as st
5
+
6
+
7
+ class TestComposableContainerVideo(unittest.TestCase):
8
+ def small_img(self, size=None):
9
+ if size is None:
10
+ size = (2, 1)
11
+ return np.ones((size[0], size[1], 3))
12
+ # return np.array(
13
+ # [[[1, 1, 1]], [[0, 0, 0]]]
14
+ # ) # represents an image of 1x2 (keep it small for speed)
15
+
16
+ def small_video(self, num_frames: int = 2, render_cfg=None, size=None):
17
+ img = self.small_img(size=size)
18
+ vid = bch.ComposableContainerVideo()
19
+ for _ in range(num_frames):
20
+ vid.append(img)
21
+ return vid.render(render_cfg)
22
+
23
+ # @given(frames=st.sampled_from([1, 2, 10, 100]))
24
+ # def test_duration_default(self, frames):
25
+ # ccv = bch.ComposableContainerVideo()
26
+ # duration, frame_duration = ccv.calculate_duration(frames, bch.RenderCfg())
27
+ # self.assertEqual(duration, 10)
28
+ # self.assertEqual(frame_duration, 10 / frames)
29
+
30
+ @given(frames=st.sampled_from([1, 2, 10, 100]), duration=st.sampled_from([0.1, 1, 10, 100]))
31
+ def test_set_duration(self, frames, duration):
32
+ ccv = bch.ComposableContainerVideo()
33
+ duration, frame_duration = ccv.calculate_duration(frames, bch.RenderCfg(duration=duration))
34
+ self.assertEqual(duration, duration)
35
+ self.assertEqual(frame_duration, duration / frames)
36
+
37
+ @given(frames=st.sampled_from([1, 2, 10, 100]), duration=st.sampled_from([0.1, 1, 10, 100]))
38
+ def test_set_duration1(self, frames, duration):
39
+ ccv = bch.ComposableContainerVideo()
40
+ min_frame_duration = 1 / 30
41
+ max_frame_duration = 2.0
42
+ duration, frame_duration = ccv.calculate_duration(
43
+ frames,
44
+ bch.RenderCfg(
45
+ duration=duration,
46
+ min_frame_duration=1 / 30,
47
+ max_frame_duration=2,
48
+ duration_target=True,
49
+ ),
50
+ )
51
+ self.assertEqual(duration, duration)
52
+ self.assertEqual(frame_duration, duration / frames)
53
+ self.assertLessEqual(frame_duration, max_frame_duration)
54
+ self.assertGreaterEqual(frame_duration, min_frame_duration)
55
+
56
+ def test_img_right(self):
57
+ res = self.small_video(1, bch.RenderCfg(duration=0.1, compose_method=bch.ComposeType.right))
58
+ self.assertEqual(res.size, (1, 2))
59
+ self.assertEqual(res.duration, 0.1)
60
+
61
+ def test_img_down(self):
62
+ res = self.small_video(1, bch.RenderCfg(duration=0.1, compose_method=bch.ComposeType.down))
63
+ self.assertEqual(res.size, (1, 2))
64
+ self.assertEqual(res.duration, 0.1)
65
+
66
+ def test_img_sequence(self):
67
+ res = self.small_video(
68
+ 1, bch.RenderCfg(duration=0.1, compose_method=bch.ComposeType.sequence)
69
+ )
70
+ self.assertEqual(res.size, (1, 2))
71
+ self.assertEqual(res.duration, 0.1)
72
+
73
+ def test_img_right_x2(self):
74
+ res = self.small_video(2, bch.RenderCfg(compose_method=bch.ComposeType.right))
75
+ self.assertEqual(res.size, (2, 2))
76
+ self.assertEqual(res.duration, 2)
77
+
78
+ def test_img_down_x2(self):
79
+ res = self.small_video(2, bch.RenderCfg(compose_method=bch.ComposeType.down))
80
+ self.assertEqual(res.size, (1, 4))
81
+ self.assertEqual(res.duration, 2)
82
+
83
+ def test_img_seq_x2(self):
84
+ res = self.small_video(2, bch.RenderCfg(compose_method=bch.ComposeType.sequence))
85
+ self.assertEqual(res.size, (1, 2))
86
+ self.assertEqual(res.duration, 4)
87
+
88
+ def test_video_seq(self):
89
+ img = self.small_img()
90
+ vid1 = bch.ComposableContainerVideo()
91
+ vid1.append(img)
92
+ vid1.append(img)
93
+
94
+ res = vid1.deep().render(
95
+ bch.RenderCfg(duration=0.1, compose_method=bch.ComposeType.sequence)
96
+ )
97
+
98
+ self.assertEqual(res.size, (1, 2))
99
+ self.assertEqual(res.duration, 0.1) # two frames
100
+
101
+ render_cfg = bch.RenderCfg(
102
+ duration=10.0,
103
+ duration_target=True,
104
+ min_frame_duration=1.0 / 30,
105
+ max_frame_duration=1.0,
106
+ )
107
+
108
+ res = vid1.deep().render(render_cfg)
109
+
110
+ self.assertEqual(res.size, (1, 2))
111
+ self.assertEqual(res.duration, 2.0) # 2 frames of minimum frame time 1.0
112
+
113
+ vid1.append(img)
114
+
115
+ res = vid1.render(render_cfg)
116
+ self.assertEqual(res.size, (1, 2))
117
+ self.assertEqual(res.duration, 3.0) # 3 frames of minimum frame time 1.0
118
+
119
+ def test_concat_equal_video_len(self):
120
+ render_cfg = bch.RenderCfg(duration_target=True)
121
+ res1 = self.small_video(num_frames=2, render_cfg=render_cfg)
122
+ res2 = self.small_video(num_frames=2, render_cfg=render_cfg)
123
+
124
+ self.assertEqual(res1.size, (1, 2))
125
+ self.assertEqual(res1.duration, 4.0) # 2 frames of minimum frame time 2.0
126
+
127
+ self.assertEqual(res2.size, (1, 2))
128
+ self.assertEqual(res2.duration, 4.0) # 1 frames of minimum frame time 2.0
129
+
130
+ vid = bch.ComposableContainerVideo()
131
+
132
+ vid.append(res1)
133
+ vid.append(res2)
134
+
135
+ res = vid.deep().render(bch.RenderCfg(bch.ComposeType.right))
136
+ self.assertEqual(res.size, (2, 2))
137
+ self.assertEqual(res.duration, 4.0) # the duration of the longer clip
138
+
139
+ res = vid.deep().render(bch.RenderCfg(bch.ComposeType.down))
140
+ self.assertEqual(res.size, (1, 4))
141
+ self.assertEqual(res.duration, 4.0) # the duration of the longer clip
142
+
143
+ res = vid.deep().render(bch.RenderCfg(bch.ComposeType.sequence))
144
+ self.assertEqual(res.size, (1, 2))
145
+ self.assertEqual(res.duration, 8.0) # the duration of both clips
146
+
147
+ def test_concat_unequal_video_len(self):
148
+ render_cfg = bch.RenderCfg(duration_target=True)
149
+ res1 = self.small_video(num_frames=2, render_cfg=render_cfg)
150
+ self.assertEqual(res1.size, (1, 2))
151
+ self.assertEqual(res1.duration, 4.0) # 3 frames of minimum frame time 1.0
152
+
153
+ res2 = self.small_video(num_frames=3, render_cfg=render_cfg)
154
+ self.assertEqual(res2.size, (1, 2))
155
+ self.assertEqual(res2.duration, 6.0) # 3 frames of minimum frame time 1.0
156
+
157
+ vid = bch.ComposableContainerVideo()
158
+
159
+ vid.append(res1)
160
+ vid.append(res2)
161
+
162
+ res = vid.deep().render(bch.RenderCfg(bch.ComposeType.right))
163
+ self.assertEqual(res.size, (2, 2))
164
+ self.assertEqual(res.duration, 6.0) # the duration of the longer clip
165
+
166
+ res = vid.deep().render(bch.RenderCfg(bch.ComposeType.down))
167
+ self.assertEqual(res.size, (1, 4))
168
+ self.assertEqual(res.duration, 6.0) # the duration of the longer clip
169
+
170
+ res = vid.deep().render(bch.RenderCfg(bch.ComposeType.sequence))
171
+ self.assertEqual(res.size, (1, 2))
172
+ self.assertEqual(res.duration, 10.0) # the duration of both clips
173
+
174
+ res_label = vid.deep().render(bch.RenderCfg(bch.ComposeType.sequence))
175
+ self.assertEqual(res.duration, res_label.duration) # the duration of both clips
176
+
177
+ def test_bad_filetype(self):
178
+ vid = bch.ComposableContainerVideo()
179
+ with self.assertRaises(RuntimeWarning):
180
+ vid.append("bad.badextension")
181
+
182
+ def test_simple_image_length(self):
183
+ ccv = bch.ComposableContainerVideo()
184
+ ccv.append(self.small_img())
185
+ ccv.append(self.small_img())
186
+
187
+ res = ccv.render()
188
+ self.assertEqual(res.duration, 4.0) # limited by maximum frame time
189
+
190
+ ccv = bch.ComposableContainerVideo()
191
+ for _ in range(20):
192
+ ccv.append(self.small_img())
193
+
194
+ self.assertEqual(ccv.render().duration, 10.0) # limited by target video duration
195
+
196
+ ccv = bch.ComposableContainerVideo()
197
+ for _ in range(200):
198
+ ccv.append(self.small_img())
199
+
200
+ # still limited by target video duration
201
+ self.assertAlmostEqual(ccv.render().duration, 10.0)
202
+
203
+ def test_composite_image_length(self):
204
+ ccv = bch.ComposableContainerVideo()
205
+
206
+ for _ in range(2):
207
+ sub_img = bch.ComposableContainerVideo()
208
+ sub_img.append(self.small_img())
209
+ sub_img.append(self.small_img())
210
+ sub_img.append(self.small_img())
211
+ ccv.append(sub_img.render(compose_method=bch.ComposeType.right))
212
+
213
+ # should be 4 because there are two sequential frames
214
+ self.assertEqual(ccv.render().duration, 4.0)
215
+
216
+ ccv = bch.ComposableContainerVideo()
217
+
218
+ for _ in range(5):
219
+ sub_img = bch.ComposableContainerVideo()
220
+ sub_img.append(self.small_img())
221
+ sub_img.append(self.small_img())
222
+ sub_img.append(self.small_img())
223
+ ccv.append(sub_img.render(compose_method=bch.ComposeType.right))
224
+
225
+ # should be 10 because of the default target length
226
+ self.assertEqual(ccv.render().duration, 10.0)
227
+
228
+ def test_video_lengths(self):
229
+ ccv = bch.ComposableContainerVideo()
230
+ ccv.append(self.small_video())
231
+ ccv.append(self.small_video())
232
+
233
+ res = ccv.render()
234
+ self.assertEqual(res.duration, 8.0) # no frame limits, just concat videos
235
+
236
+ ccv = bch.ComposableContainerVideo()
237
+ for _ in range(20):
238
+ ccv.append(self.small_video())
239
+
240
+ self.assertEqual(ccv.render().duration, 80.0) # concatted vid time
241
+
242
+ ccv = bch.ComposableContainerVideo()
243
+ for _ in range(200):
244
+ ccv.append(self.small_video())
245
+
246
+ # still concatted vid time
247
+ self.assertAlmostEqual(ccv.render().duration, 800.0)
248
+
249
+
250
+ if __name__ == "__main__":
251
+ tst = TestComposableContainerVideo()
252
+
253
+ tst.test_img_right()
254
+
255
+ unittest.main()
@@ -9,6 +9,7 @@ from bencher.utils import (
9
9
  lerp,
10
10
  listify,
11
11
  tabs_in_markdown,
12
+ mult_tuple,
12
13
  )
13
14
  from functools import partial
14
15
  import xarray as xr
@@ -78,6 +79,9 @@ class TestBencherUtils(unittest.TestCase):
78
79
  bch.hmap_canonical_input(dic2),
79
80
  )
80
81
 
82
+ def test_mult_tuple(self) -> None:
83
+ self.assertTupleEqual(mult_tuple((1, 2, 3), 2), (2, 4, 6))
84
+
81
85
  # Tests that the function returns the nearest coordinate name value pair for a dataset containing multiple coordinates
82
86
  def test_multiple_coordinates(self):
83
87
  ds = xr.Dataset({"x": [1, 2, 3], "y": [4, 5, 6]})
@@ -1,121 +0,0 @@
1
- import numpy as np
2
- import moviepy.video.io.ImageSequenceClip
3
- from pathlib import Path
4
- from .utils import gen_video_path, gen_image_path
5
-
6
- import moviepy
7
- from moviepy.editor import (
8
- VideoFileClip,
9
- ImageClip,
10
- ImageSequenceClip,
11
- clips_array,
12
- concatenate_videoclips,
13
- )
14
- from PIL import Image, ImageDraw
15
-
16
-
17
- class VideoWriter:
18
- def __init__(self, filename: str = "vid") -> None:
19
- self.images = []
20
- self.image_files = []
21
- self.video_files = []
22
- self.filename = gen_video_path(filename)
23
-
24
- def append(self, img):
25
- self.images.append(img)
26
-
27
- def write(self) -> str:
28
- clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(
29
- self.images, fps=30, with_mask=False, load_images=True
30
- )
31
- self.write_video_raw(clip)
32
- return self.filename
33
-
34
- @staticmethod
35
- def create_label(label, width=None, height=14):
36
- if width is None:
37
- width = len(label) * 8
38
- new_img = Image.new("RGB", (width, height), (255, 255, 255))
39
- # ImageDraw.Draw(new_img).text((width/2, 0), label, (0, 0, 0),align="center",achor="ms")
40
- ImageDraw.Draw(new_img).text((width / 2.0, 0), label, (0, 0, 0), anchor="mt", font_size=12)
41
-
42
- return new_img
43
-
44
- @staticmethod
45
- def label_image(path: Path, label, padding=20) -> Path:
46
- image = Image.open(path)
47
- new_img = VideoWriter.create_label(label, image.size[0], image.size[1] + padding)
48
- new_img.paste(image, (0, padding))
49
- return new_img
50
-
51
- def append_file(self, filepath, label=None):
52
- if label is not None:
53
- path = Path(filepath)
54
- new_path = path.with_name(path.stem + "_labelled" + path.suffix).as_posix()
55
- padding = 20
56
- match path.suffix:
57
- case ".png" | ".jpg":
58
- image = Image.open(filepath)
59
- new_img = self.create_label(label, image.size[0], image.size[1] + padding)
60
- new_img.paste(image, (0, padding))
61
- new_img.save(new_path)
62
- self.image_files.append(new_path)
63
- case ".webm":
64
- import warnings
65
-
66
- video_clip = VideoFileClip(filepath)
67
- new_img = self.create_label(label, video_clip.w, padding)
68
-
69
- # Convert PIL image to MoviePy clip
70
- label_clip = ImageClip(np.array(new_img), duration=video_clip.duration)
71
-
72
- labeled_video_clip = clips_array([[label_clip], [video_clip]])
73
-
74
- # otherwise ffmpeg complains that the file is not getting read. We don't need the file just the size
75
- with warnings.catch_warnings():
76
- warnings.simplefilter(action="ignore")
77
- labeled_video_clip.write_videofile(new_path, remove_temp=True, logger=None)
78
- self.video_files.append(new_path)
79
- else:
80
- self.image_files.append(filepath)
81
-
82
- def to_images_sequence(self, images, target_duration: float = 10.0, frame_time=None, **kwargs):
83
- target_duration = kwargs.pop("target_duration", target_duration)
84
- if isinstance(images, list) and len(images) > 0:
85
- if frame_time is None:
86
- fps = len(images) / target_duration
87
- fps = max(fps, 1) # never slower that 1 seconds per frame
88
- fps = min(fps, 30)
89
- else:
90
- fps = 1.0 / frame_time
91
- return ImageSequenceClip(images, fps=fps, with_mask=False)
92
- return None
93
-
94
- def write_png(self, **kwargs):
95
- clip = None
96
- if len(self.image_files) > 0:
97
- clip = self.to_images_sequence(self.image_files, **kwargs)
98
- if len(self.video_files) > 0:
99
- clip = concatenate_videoclips([VideoFileClip(f) for f in self.video_files])
100
- if clip is not None:
101
- clip.write_videofile(self.filename)
102
- return self.filename
103
- return None
104
-
105
- def write_video_raw(self, video_clip: moviepy.video.VideoClip, fps: int = 30) -> str:
106
- video_clip.write_videofile(
107
- self.filename,
108
- codec="libvpx",
109
- audio=False,
110
- bitrate="0",
111
- fps=fps,
112
- ffmpeg_params=["-crf", "34"],
113
- )
114
- video_clip.close()
115
- return self.filename
116
-
117
-
118
- def add_image(np_array: np.ndarray, name: str = "img"):
119
- filename = gen_image_path(name)
120
- Image.fromarray(np_array).save(filename)
121
- return filename
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes