holobench 1.22.2__tar.gz → 1.23.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.
- {holobench-1.22.2/holobench.egg-info → holobench-1.23.0}/PKG-INFO +7 -5
- {holobench-1.22.2 → holobench-1.23.0}/bencher/__init__.py +9 -0
- {holobench-1.22.2 → holobench-1.23.0}/bencher/bench_runner.py +1 -1
- {holobench-1.22.2 → holobench-1.23.0}/bencher/bencher.py +3 -1
- {holobench-1.22.2 → holobench-1.23.0}/bencher/optuna_conversions.py +3 -2
- {holobench-1.22.2 → holobench-1.23.0}/bencher/utils.py +15 -2
- holobench-1.23.0/bencher/video_writer.py +67 -0
- {holobench-1.22.2 → holobench-1.23.0/holobench.egg-info}/PKG-INFO +7 -5
- {holobench-1.22.2 → holobench-1.23.0}/holobench.egg-info/SOURCES.txt +2 -0
- {holobench-1.22.2 → holobench-1.23.0}/holobench.egg-info/requires.txt +4 -4
- {holobench-1.22.2 → holobench-1.23.0}/pyproject.toml +15 -9
- {holobench-1.22.2 → holobench-1.23.0}/setup.py +2 -5
- holobench-1.23.0/test/test_composable_container_base.py +77 -0
- holobench-1.23.0/test/test_composable_container_video.py +255 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_utils.py +4 -0
- holobench-1.22.2/bencher/video_writer.py +0 -121
- {holobench-1.22.2 → holobench-1.23.0}/LICENSE +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/README.md +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/bencher/bench_cfg.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/bencher/bench_plot_server.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/bencher/bench_report.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/bencher/caching.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/bencher/class_enum.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/bencher/job.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/bencher/worker_job.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/holobench.egg-info/dependency_links.txt +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/holobench.egg-info/not-zip-safe +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/holobench.egg-info/top_level.txt +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/package.xml +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/resource/bencher +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/setup.cfg +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_bench_examples.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_bench_report.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_bench_result_base.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_bench_runner.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_bencher.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_cache.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_class_enum.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_combinations.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_float_formatter.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_job.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_meta_tests.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_parametrized_sweep.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_plot_filter.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_plot_server.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_sample_cache.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_sweep_base.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_sweep_vars.py +0 -0
- {holobench-1.22.2 → holobench-1.23.0}/test/test_vars.py +0 -0
@@ -1,8 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: holobench
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.23.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.
|
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.
|
34
|
-
Requires-Dist: ruff<=0.4.
|
35
|
-
Requires-Dist: coverage<=7.5.
|
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))
|
@@ -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
|
-
|
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   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 = ".
|
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.
|
3
|
+
Version: 1.23.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.
|
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.
|
34
|
-
Requires-Dist: ruff<=0.4.
|
35
|
-
Requires-Dist: coverage<=7.5.
|
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.
|
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.
|
25
|
-
ruff<=0.4.
|
26
|
-
coverage<=7.5.
|
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.
|
3
|
+
version = "1.23.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.
|
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.
|
52
|
-
"ruff>=0.3,<=0.4.
|
53
|
-
"coverage>=7.2.7,<=7.5.
|
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,26 @@ 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
|
+
push = "git push"
|
84
|
+
update-lock-push = { depends_on = ["update-lock", "push"] }
|
81
85
|
ci-no-cover = { depends_on = ["style", "test"] }
|
82
86
|
ci = { depends_on = [
|
83
|
-
"
|
87
|
+
"format",
|
88
|
+
"ruff-lint",
|
84
89
|
"check-clean-workspace",
|
90
|
+
"pylint",
|
85
91
|
"coverage",
|
86
92
|
"coverage-report",
|
87
93
|
] }
|
88
|
-
|
94
|
+
ci-push = { depends_on = ["format", "ruff-lint", "update-lock", "ci", "push"] }
|
89
95
|
clear-pixi = "rm -rf .pixi pixi.lock"
|
90
96
|
|
91
97
|
|
@@ -97,7 +103,7 @@ extension-pkg-whitelist = ["numpy", "scipy"]
|
|
97
103
|
jobs = 16 #detect number of cores
|
98
104
|
|
99
105
|
[tool.pylint.'MESSAGES CONTROL']
|
100
|
-
disable = "C,logging-fstring-interpolation,line-too-long,fixme,
|
106
|
+
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
107
|
enable = "no-else-return,consider-using-in"
|
102
108
|
|
103
109
|
[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
|
-
|
10
|
-
|
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"],
|
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|