holobench 1.40.1__py3-none-any.whl → 1.42.0__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.
- CHANGELOG.md +10 -0
- bencher/__init__.py +20 -2
- bencher/bench_cfg.py +265 -61
- bencher/bench_report.py +2 -2
- bencher/bench_runner.py +96 -10
- bencher/bencher.py +421 -89
- bencher/caching.py +1 -4
- bencher/class_enum.py +70 -7
- bencher/example/example_composable_container_image.py +60 -0
- bencher/example/example_composable_container_video.py +49 -0
- bencher/example/example_dataframe.py +2 -2
- bencher/example/example_image.py +17 -21
- bencher/example/example_image1.py +16 -20
- bencher/example/example_levels.py +17 -173
- bencher/example/example_pareto.py +107 -31
- bencher/example/example_rerun2.py +1 -1
- bencher/example/example_simple_bool.py +2 -2
- bencher/example/example_simple_float2d.py +6 -1
- bencher/example/example_video.py +35 -17
- bencher/example/experimental/example_hvplot_explorer.py +3 -4
- bencher/example/inputs_0D/example_0_in_1_out.py +25 -15
- bencher/example/inputs_0D/example_0_in_2_out.py +12 -3
- bencher/example/inputs_0_float/example_0_cat_in_2_out.py +88 -0
- bencher/example/inputs_0_float/example_1_cat_in_2_out.py +98 -0
- bencher/example/inputs_0_float/example_2_cat_in_2_out.py +107 -0
- bencher/example/inputs_0_float/example_3_cat_in_2_out.py +111 -0
- bencher/example/inputs_1D/example1d_common.py +48 -12
- bencher/example/inputs_1D/example_0_float_1_cat.py +33 -0
- bencher/example/inputs_1D/example_1_cat_in_2_out_repeats.py +68 -0
- bencher/example/inputs_1D/example_1_float_2_cat_repeats.py +15 -0
- bencher/example/inputs_1D/example_1_int_in_1_out.py +98 -0
- bencher/example/inputs_1D/example_1_int_in_2_out.py +101 -0
- bencher/example/inputs_1D/example_1_int_in_2_out_repeats.py +99 -0
- bencher/example/inputs_1_float/example_1_float_0_cat_in_2_out.py +117 -0
- bencher/example/inputs_1_float/example_1_float_1_cat_in_2_out.py +124 -0
- bencher/example/inputs_1_float/example_1_float_2_cat_in_2_out.py +132 -0
- bencher/example/inputs_1_float/example_1_float_3_cat_in_2_out.py +140 -0
- bencher/example/inputs_2D/example_2_cat_in_4_out_repeats.py +104 -0
- bencher/example/inputs_2_float/example_2_float_0_cat_in_2_out.py +98 -0
- bencher/example/inputs_2_float/example_2_float_1_cat_in_2_out.py +112 -0
- bencher/example/inputs_2_float/example_2_float_2_cat_in_2_out.py +122 -0
- bencher/example/inputs_2_float/example_2_float_3_cat_in_2_out.py +138 -0
- bencher/example/inputs_3_float/example_3_float_0_cat_in_2_out.py +111 -0
- bencher/example/inputs_3_float/example_3_float_1_cat_in_2_out.py +117 -0
- bencher/example/inputs_3_float/example_3_float_2_cat_in_2_out.py +124 -0
- bencher/example/inputs_3_float/example_3_float_3_cat_in_2_out.py +129 -0
- bencher/example/meta/generate_examples.py +124 -7
- bencher/example/meta/generate_meta.py +88 -40
- bencher/job.py +175 -12
- bencher/plotting/plot_filter.py +52 -17
- bencher/results/bench_result.py +119 -26
- bencher/results/bench_result_base.py +119 -10
- bencher/results/composable_container/composable_container_video.py +39 -12
- bencher/results/dataset_result.py +6 -200
- bencher/results/explorer_result.py +23 -0
- bencher/results/{hvplot_result.py → histogram_result.py} +3 -18
- bencher/results/holoview_results/__init__.py +0 -0
- bencher/results/holoview_results/bar_result.py +79 -0
- bencher/results/holoview_results/curve_result.py +110 -0
- bencher/results/holoview_results/distribution_result/__init__.py +0 -0
- bencher/results/holoview_results/distribution_result/box_whisker_result.py +73 -0
- bencher/results/holoview_results/distribution_result/distribution_result.py +109 -0
- bencher/results/holoview_results/distribution_result/scatter_jitter_result.py +92 -0
- bencher/results/holoview_results/distribution_result/violin_result.py +70 -0
- bencher/results/holoview_results/heatmap_result.py +319 -0
- bencher/results/holoview_results/holoview_result.py +346 -0
- bencher/results/holoview_results/line_result.py +240 -0
- bencher/results/holoview_results/scatter_result.py +107 -0
- bencher/results/holoview_results/surface_result.py +158 -0
- bencher/results/holoview_results/table_result.py +14 -0
- bencher/results/holoview_results/tabulator_result.py +20 -0
- bencher/results/laxtex_result.py +42 -35
- bencher/results/optuna_result.py +30 -115
- bencher/results/video_controls.py +38 -0
- bencher/results/video_result.py +39 -36
- bencher/results/video_summary.py +2 -2
- bencher/results/{plotly_result.py → volume_result.py} +29 -8
- bencher/utils.py +176 -30
- bencher/variables/inputs.py +122 -15
- bencher/video_writer.py +38 -2
- bencher/worker_job.py +34 -7
- {holobench-1.40.1.dist-info → holobench-1.42.0.dist-info}/METADATA +21 -25
- holobench-1.42.0.dist-info/RECORD +147 -0
- bencher/example/example_composable_container.py +0 -106
- bencher/example/example_levels2.py +0 -37
- bencher/example/inputs_1D/example_1_in_1_out.py +0 -62
- bencher/example/inputs_1D/example_1_in_2_out.py +0 -63
- bencher/example/inputs_1D/example_1_in_2_out_repeats.py +0 -61
- bencher/results/holoview_result.py +0 -787
- bencher/results/panel_result.py +0 -41
- holobench-1.40.1.dist-info/RECORD +0 -111
- {holobench-1.40.1.dist-info → holobench-1.42.0.dist-info}/WHEEL +0 -0
- {holobench-1.40.1.dist-info → holobench-1.42.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,29 +1,50 @@
|
|
1
|
-
import
|
2
|
-
import plotly.graph_objs as go
|
3
|
-
from typing import Optional
|
4
|
-
import xarray as xr
|
1
|
+
from typing import Optional, Any
|
5
2
|
|
3
|
+
import xarray as xr
|
6
4
|
from param import Parameter
|
5
|
+
import panel as pn
|
6
|
+
import plotly.graph_objs as go
|
7
7
|
|
8
8
|
from bencher.plotting.plot_filter import VarRange
|
9
9
|
from bencher.results.bench_result_base import BenchResultBase, ReduceType
|
10
10
|
from bencher.variables.results import ResultVar
|
11
11
|
|
12
12
|
|
13
|
-
class
|
14
|
-
def
|
13
|
+
class VolumeResult(BenchResultBase):
|
14
|
+
def to_plot(
|
15
|
+
self, result_var: Optional[Parameter] = None, override: bool = True, **kwargs: Any
|
16
|
+
) -> Optional[pn.panel]:
|
17
|
+
"""Generates a 3d volume plot from benchmark data.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
result_var (Optional[Parameter]): The result variable to plot. If None, uses the default.
|
21
|
+
override (bool): Whether to override filter restrictions. Defaults to True.
|
22
|
+
**kwargs (Any): Additional keyword arguments passed to the plot rendering.
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
Optional[pn.panel]: A panel containing the volume plot if data is appropriate,
|
26
|
+
otherwise returns filter match results.
|
27
|
+
"""
|
28
|
+
return self.to_volume(
|
29
|
+
result_var=result_var,
|
30
|
+
override=override,
|
31
|
+
**kwargs,
|
32
|
+
)
|
33
|
+
|
34
|
+
def to_volume(self, result_var: Parameter = None, override: bool = True, **kwargs):
|
15
35
|
return self.filter(
|
16
|
-
self.
|
36
|
+
self.to_volume_ds,
|
17
37
|
float_range=VarRange(3, 3),
|
18
38
|
cat_range=VarRange(-1, 0),
|
19
39
|
reduce=ReduceType.REDUCE,
|
20
40
|
target_dimension=3,
|
21
41
|
result_var=result_var,
|
22
42
|
result_types=(ResultVar),
|
43
|
+
override=override,
|
23
44
|
**kwargs,
|
24
45
|
)
|
25
46
|
|
26
|
-
def
|
47
|
+
def to_volume_ds(
|
27
48
|
self, dataset: xr.Dataset, result_var: Parameter, width=600, height=600
|
28
49
|
) -> Optional[pn.pane.Plotly]:
|
29
50
|
"""Given a benchCfg generate a 3D surface plot
|
bencher/utils.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from collections import namedtuple
|
2
2
|
import xarray as xr
|
3
|
-
from sortedcontainers import SortedDict
|
4
3
|
import hashlib
|
5
4
|
import re
|
6
5
|
import math
|
@@ -27,9 +26,7 @@ def hmap_canonical_input(dic: dict) -> tuple:
|
|
27
26
|
Returns:
|
28
27
|
tuple: values of the dictionary always in the same order and hashable
|
29
28
|
"""
|
30
|
-
|
31
|
-
function_input = SortedDict(dic)
|
32
|
-
return tuple(function_input.values())
|
29
|
+
return tuple(value for _, value in sorted(dic.items()))
|
33
30
|
|
34
31
|
|
35
32
|
def make_namedtuple(class_name: str, **fields) -> namedtuple:
|
@@ -44,16 +41,22 @@ def make_namedtuple(class_name: str, **fields) -> namedtuple:
|
|
44
41
|
return namedtuple(class_name, fields)(*fields.values())
|
45
42
|
|
46
43
|
|
47
|
-
def get_nearest_coords(dataset: xr.Dataset, collapse_list=False, **kwargs) -> dict:
|
48
|
-
"""
|
44
|
+
def get_nearest_coords(dataset: xr.Dataset, collapse_list: bool = False, **kwargs) -> dict:
|
45
|
+
"""Find the nearest coordinates in an xarray dataset based on provided coordinate values.
|
46
|
+
|
47
|
+
Given an xarray dataset and kwargs of key-value pairs of coordinate values, return a dictionary
|
48
|
+
of the nearest coordinate name-value pair that was found in the dataset.
|
49
49
|
|
50
50
|
Args:
|
51
|
-
|
51
|
+
dataset (xr.Dataset): The xarray dataset to search in
|
52
|
+
collapse_list (bool, optional): If True, when a coordinate value is a list, only the first
|
53
|
+
item is returned. Defaults to False.
|
54
|
+
**kwargs: Key-value pairs where keys are coordinate names and values are points to find
|
55
|
+
the nearest match for
|
52
56
|
|
53
57
|
Returns:
|
54
|
-
dict:
|
58
|
+
dict: Dictionary of coordinate name-value pairs with the nearest values found in the dataset
|
55
59
|
"""
|
56
|
-
|
57
60
|
selection = dataset.sel(method="nearest", **kwargs)
|
58
61
|
cd = selection.coords.to_dataset().to_dict()["coords"]
|
59
62
|
cd2 = {}
|
@@ -64,7 +67,19 @@ def get_nearest_coords(dataset: xr.Dataset, collapse_list=False, **kwargs) -> di
|
|
64
67
|
return cd2
|
65
68
|
|
66
69
|
|
67
|
-
def get_nearest_coords1D(val: Any, coords) -> Any:
|
70
|
+
def get_nearest_coords1D(val: Any, coords: List[Any]) -> Any:
|
71
|
+
"""Find the closest coordinate to a given value in a list of coordinates.
|
72
|
+
|
73
|
+
For numeric values, finds the value in coords that is closest to val.
|
74
|
+
For non-numeric values, returns the exact match if found, otherwise returns val.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
val (Any): The value to find the closest coordinate for
|
78
|
+
coords (List[Any]): The list of coordinates to search in
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
Any: The closest coordinate value from the list
|
82
|
+
"""
|
68
83
|
if isinstance(val, (int, float)):
|
69
84
|
return min(coords, key=lambda x_: abs(x_ - val))
|
70
85
|
for i in coords:
|
@@ -73,19 +88,28 @@ def get_nearest_coords1D(val: Any, coords) -> Any:
|
|
73
88
|
return val
|
74
89
|
|
75
90
|
|
76
|
-
def hash_sha1(var:
|
77
|
-
"""A hash function that avoids the PYTHONHASHSEED 'feature' which returns a different hash value each time the program is run
|
91
|
+
def hash_sha1(var: Any) -> str:
|
92
|
+
"""A hash function that avoids the PYTHONHASHSEED 'feature' which returns a different hash value each time the program is run.
|
93
|
+
|
94
|
+
Converts input to a consistent SHA1 hash string.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
var (Any): The variable to hash
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
str: A hexadecimal SHA1 hash of the string representation of the variable
|
101
|
+
"""
|
78
102
|
return hashlib.sha1(str(var).encode("ASCII")).hexdigest()
|
79
103
|
|
80
104
|
|
81
|
-
def capitalise_words(message: str):
|
82
|
-
"""Given a string of lowercase words, capitalise them
|
105
|
+
def capitalise_words(message: str) -> str:
|
106
|
+
"""Given a string of lowercase words, capitalise them.
|
83
107
|
|
84
108
|
Args:
|
85
109
|
message (str): lower case string
|
86
110
|
|
87
111
|
Returns:
|
88
|
-
|
112
|
+
str: capitalised string where each word starts with an uppercase letter
|
89
113
|
"""
|
90
114
|
capitalized_message = " ".join([word.capitalize() for word in message.split(" ")])
|
91
115
|
return capitalized_message
|
@@ -104,7 +128,16 @@ def un_camel(camel: str) -> str:
|
|
104
128
|
return capitalise_words(re.sub("([a-z])([A-Z])", r"\g<1> \g<2>", camel.replace("_", " ")))
|
105
129
|
|
106
130
|
|
107
|
-
def mult_tuple(inp: Tuple[float], val: float) -> Tuple[float]:
|
131
|
+
def mult_tuple(inp: Tuple[float, ...], val: float) -> Tuple[float, ...]:
|
132
|
+
"""Multiply each element in a tuple by a scalar value.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
inp (Tuple[float, ...]): The input tuple of floats to multiply
|
136
|
+
val (float): The scalar value to multiply each element by
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
Tuple[float, ...]: A new tuple with each element multiplied by val
|
140
|
+
"""
|
108
141
|
return tuple(np.array(inp) * val)
|
109
142
|
|
110
143
|
|
@@ -121,15 +154,17 @@ def tabs_in_markdown(regular_str: str, spaces: int = 2) -> str:
|
|
121
154
|
return regular_str.replace("\t", "".join([" "] * spaces))
|
122
155
|
|
123
156
|
|
124
|
-
def int_to_col(
|
157
|
+
def int_to_col(
|
158
|
+
int_val: int, sat: float = 0.5, val: float = 0.95, alpha: float = -1
|
159
|
+
) -> tuple[float, float, float] | tuple[float, float, float, float]:
|
125
160
|
"""Uses the golden angle to generate colors programmatically with minimum overlap between colors.
|
126
161
|
https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
|
127
162
|
|
128
163
|
Args:
|
129
|
-
int_val (
|
164
|
+
int_val (int): index of an object you want to color, this is mapped to hue in HSV
|
130
165
|
sat (float, optional): saturation in HSV. Defaults to 0.5.
|
131
166
|
val (float, optional): value in HSV. Defaults to 0.95.
|
132
|
-
alpha (
|
167
|
+
alpha (float, optional): transparency. If -1 then only RGB is returned, if 0 or greater, RGBA is returned. Defaults to -1.
|
133
168
|
|
134
169
|
Returns:
|
135
170
|
tuple[float, float, float] | tuple[float, float, float, float]: either RGB or RGBA vector
|
@@ -141,7 +176,23 @@ def int_to_col(int_val, sat=0.5, val=0.95, alpha=-1) -> tuple[float, float, floa
|
|
141
176
|
return rgb
|
142
177
|
|
143
178
|
|
144
|
-
def lerp(
|
179
|
+
def lerp(
|
180
|
+
value: float, input_low: float, input_high: float, output_low: float, output_high: float
|
181
|
+
) -> float:
|
182
|
+
"""Linear interpolation between two ranges.
|
183
|
+
|
184
|
+
Maps a value from one range [input_low, input_high] to another range [output_low, output_high].
|
185
|
+
|
186
|
+
Args:
|
187
|
+
value (float): The input value to interpolate
|
188
|
+
input_low (float): The lower bound of the input range
|
189
|
+
input_high (float): The upper bound of the input range
|
190
|
+
output_low (float): The lower bound of the output range
|
191
|
+
output_high (float): The upper bound of the output range
|
192
|
+
|
193
|
+
Returns:
|
194
|
+
float: The interpolated value in the output range
|
195
|
+
"""
|
145
196
|
input_low = float(input_low)
|
146
197
|
return output_low + ((float(value) - input_low) / (float(input_high) - input_low)) * (
|
147
198
|
float(output_high) - output_low
|
@@ -149,10 +200,26 @@ def lerp(value, input_low: float, input_high: float, output_low: float, output_h
|
|
149
200
|
|
150
201
|
|
151
202
|
def color_tuple_to_css(color: tuple[float, float, float]) -> str:
|
203
|
+
"""Convert a RGB color tuple to CSS rgb format string.
|
204
|
+
|
205
|
+
Args:
|
206
|
+
color (tuple[float, float, float]): RGB color tuple with values in range [0.0, 1.0]
|
207
|
+
|
208
|
+
Returns:
|
209
|
+
str: CSS color string in format 'rgb(r, g, b)' with values in range [0, 255]
|
210
|
+
"""
|
152
211
|
return f"rgb{(color[0] * 255, color[1] * 255, color[2] * 255)}"
|
153
212
|
|
154
213
|
|
155
|
-
def color_tuple_to_255(color: tuple[float, float, float]) -> tuple[
|
214
|
+
def color_tuple_to_255(color: tuple[float, float, float]) -> tuple[int, int, int]:
|
215
|
+
"""Convert a RGB color tuple with values in range [0.0, 1.0] to values in range [0, 255].
|
216
|
+
|
217
|
+
Args:
|
218
|
+
color (tuple[float, float, float]): RGB color tuple with values in range [0.0, 1.0]
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
tuple[int, int, int]: RGB color tuple with values clamped to range [0, 255]
|
222
|
+
"""
|
156
223
|
return (
|
157
224
|
min(int(color[0] * 255), 255),
|
158
225
|
min(int(color[1] * 255), 255),
|
@@ -160,25 +227,76 @@ def color_tuple_to_255(color: tuple[float, float, float]) -> tuple[float, float,
|
|
160
227
|
)
|
161
228
|
|
162
229
|
|
163
|
-
def gen_path(filename, folder="generic", suffix=".dat"):
|
230
|
+
def gen_path(filename: str, folder: str = "generic", suffix: str = ".dat") -> str:
|
231
|
+
"""Generate a unique path for a file in the cache directory.
|
232
|
+
|
233
|
+
Creates a directory structure in the 'cachedir' folder and returns a path
|
234
|
+
with a UUID to ensure uniqueness.
|
235
|
+
|
236
|
+
Args:
|
237
|
+
filename (str): Base name for the file
|
238
|
+
folder (str, optional): Subfolder within cachedir. Defaults to "generic".
|
239
|
+
suffix (str, optional): File extension. Defaults to ".dat".
|
240
|
+
|
241
|
+
Returns:
|
242
|
+
str: Absolute path to a unique file location
|
243
|
+
"""
|
164
244
|
path = Path(f"cachedir/{folder}/{filename}/")
|
165
245
|
path.mkdir(parents=True, exist_ok=True)
|
166
246
|
return f"{path.absolute().as_posix()}/{filename}_{uuid4()}{suffix}"
|
167
247
|
|
168
248
|
|
169
249
|
def gen_video_path(video_name: str = "vid", extension: str = ".mp4") -> str:
|
250
|
+
"""Generate a unique path for a video file in the cache directory.
|
251
|
+
|
252
|
+
Args:
|
253
|
+
video_name (str, optional): Base name for the video file. Defaults to "vid".
|
254
|
+
extension (str, optional): Video file extension. Defaults to ".mp4".
|
255
|
+
|
256
|
+
Returns:
|
257
|
+
str: Absolute path to a unique video file location
|
258
|
+
"""
|
170
259
|
return gen_path(video_name, "vid", extension)
|
171
260
|
|
172
261
|
|
173
|
-
def gen_image_path(image_name: str = "img", filetype=".png") -> str:
|
262
|
+
def gen_image_path(image_name: str = "img", filetype: str = ".png") -> str:
|
263
|
+
"""Generate a unique path for an image file in the cache directory.
|
264
|
+
|
265
|
+
Args:
|
266
|
+
image_name (str, optional): Base name for the image file. Defaults to "img".
|
267
|
+
filetype (str, optional): Image file extension. Defaults to ".png".
|
268
|
+
|
269
|
+
Returns:
|
270
|
+
str: Absolute path to a unique image file location
|
271
|
+
"""
|
174
272
|
return gen_path(image_name, "img", filetype)
|
175
273
|
|
176
274
|
|
177
|
-
def gen_rerun_data_path(rrd_name: str = "rrd", filetype=".rrd") -> str:
|
275
|
+
def gen_rerun_data_path(rrd_name: str = "rrd", filetype: str = ".rrd") -> str:
|
276
|
+
"""Generate a unique path for a rerun data file in the cache directory.
|
277
|
+
|
278
|
+
Args:
|
279
|
+
rrd_name (str, optional): Base name for the rerun data file. Defaults to "rrd".
|
280
|
+
filetype (str, optional): File extension. Defaults to ".rrd".
|
281
|
+
|
282
|
+
Returns:
|
283
|
+
str: Absolute path to a unique rerun data file location
|
284
|
+
"""
|
178
285
|
return gen_path(rrd_name, "rrd", filetype)
|
179
286
|
|
180
287
|
|
181
288
|
def callable_name(any_callable: Callable[..., Any]) -> str:
|
289
|
+
"""Extract the name of a callable object, handling various callable types.
|
290
|
+
|
291
|
+
This function attempts to extract the name of a callable object, including
|
292
|
+
regular functions, partial functions, and other callables.
|
293
|
+
|
294
|
+
Args:
|
295
|
+
any_callable (Callable[..., Any]): The callable object to get the name from
|
296
|
+
|
297
|
+
Returns:
|
298
|
+
str: The name of the callable
|
299
|
+
"""
|
182
300
|
if isinstance(any_callable, partial):
|
183
301
|
return any_callable.func.__name__
|
184
302
|
try:
|
@@ -187,8 +305,20 @@ def callable_name(any_callable: Callable[..., Any]) -> str:
|
|
187
305
|
return str(any_callable)
|
188
306
|
|
189
307
|
|
190
|
-
def listify(obj) ->
|
191
|
-
"""
|
308
|
+
def listify(obj: Any) -> List[Any] | None:
|
309
|
+
"""Convert an object to a list if it's not already a list.
|
310
|
+
|
311
|
+
This function handles conversion of various object types to lists, with special
|
312
|
+
handling for None values and existing list/tuple types.
|
313
|
+
|
314
|
+
Args:
|
315
|
+
obj (Any): The object to convert to a list
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
List[Any] | None: A list containing the object, the object itself if it was
|
319
|
+
already a list, a list from the tuple if it was a tuple, or None if the
|
320
|
+
input was None
|
321
|
+
"""
|
192
322
|
if obj is None:
|
193
323
|
return None
|
194
324
|
if isinstance(obj, list):
|
@@ -198,13 +328,29 @@ def listify(obj) -> list:
|
|
198
328
|
return [obj]
|
199
329
|
|
200
330
|
|
201
|
-
def get_name(var):
|
331
|
+
def get_name(var: Any) -> str:
|
332
|
+
"""Extract the name from a variable, handling param.Parameter objects.
|
333
|
+
|
334
|
+
Args:
|
335
|
+
var (Any): The variable to extract the name from
|
336
|
+
|
337
|
+
Returns:
|
338
|
+
str: The name of the variable
|
339
|
+
"""
|
202
340
|
if isinstance(var, param.Parameter):
|
203
341
|
return var.name
|
204
342
|
return var
|
205
343
|
|
206
344
|
|
207
|
-
def params_to_str(param_list: List[param.Parameter]):
|
345
|
+
def params_to_str(param_list: List[param.Parameter]) -> List[str]:
|
346
|
+
"""Convert a list of param.Parameter objects to a list of their names.
|
347
|
+
|
348
|
+
Args:
|
349
|
+
param_list (List[param.Parameter]): List of parameter objects
|
350
|
+
|
351
|
+
Returns:
|
352
|
+
List[str]: List of parameter names
|
353
|
+
"""
|
208
354
|
return [get_name(i) for i in param_list]
|
209
355
|
|
210
356
|
|
@@ -215,8 +361,8 @@ def publish_file(filepath: str, remote: str, branch_name: str) -> str: # pragma
|
|
215
361
|
|
216
362
|
def publish_args(branch_name) -> Tuple[str, str]:
|
217
363
|
return (
|
218
|
-
"https://github.com/
|
219
|
-
f"https://github.com/
|
364
|
+
"https://github.com/blooop/bencher.git",
|
365
|
+
f"https://github.com/blooop/bencher/blob/{branch_name}")
|
220
366
|
|
221
367
|
|
222
368
|
Args:
|
bencher/variables/inputs.py
CHANGED
@@ -7,7 +7,15 @@ from bencher.variables.sweep_base import SweepBase, shared_slots
|
|
7
7
|
|
8
8
|
|
9
9
|
class SweepSelector(Selector, SweepBase):
|
10
|
-
"""A class
|
10
|
+
"""A class representing a parameter sweep for selectable options.
|
11
|
+
|
12
|
+
This class extends both Selector and SweepBase to provide parameter sweeping
|
13
|
+
capabilities for categorical variables that have a predefined set of options.
|
14
|
+
|
15
|
+
Attributes:
|
16
|
+
units (str): The units of measurement for the parameter
|
17
|
+
samples (int): The number of samples to take from the available options
|
18
|
+
"""
|
11
19
|
|
12
20
|
__slots__ = shared_slots
|
13
21
|
|
@@ -22,14 +30,26 @@ class SweepSelector(Selector, SweepBase):
|
|
22
30
|
self.samples = samples
|
23
31
|
|
24
32
|
def values(self) -> List[Any]:
|
25
|
-
"""
|
33
|
+
"""Return all the values for the parameter sweep.
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
List[Any]: A list of parameter values to sweep through
|
37
|
+
"""
|
26
38
|
return self.indices_to_samples(self.samples, self.objects)
|
27
39
|
|
28
40
|
|
29
41
|
class BoolSweep(SweepSelector):
|
30
|
-
"""A class
|
42
|
+
"""A class representing a parameter sweep for boolean values.
|
43
|
+
|
44
|
+
This class extends SweepSelector to provide parameter sweeping capabilities
|
45
|
+
specifically for boolean values (True and False).
|
31
46
|
|
32
|
-
|
47
|
+
Attributes:
|
48
|
+
units (str): The units of measurement for the parameter
|
49
|
+
samples (int): The number of samples to take (typically 2 for booleans)
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(self, units: str = "ul", samples: int = None, default: bool = True, **params):
|
33
53
|
SweepSelector.__init__(
|
34
54
|
self,
|
35
55
|
units=units,
|
@@ -41,7 +61,15 @@ class BoolSweep(SweepSelector):
|
|
41
61
|
|
42
62
|
|
43
63
|
class StringSweep(SweepSelector):
|
44
|
-
"""A class
|
64
|
+
"""A class representing a parameter sweep for string values.
|
65
|
+
|
66
|
+
This class extends SweepSelector to provide parameter sweeping capabilities
|
67
|
+
specifically for a list of string values.
|
68
|
+
|
69
|
+
Attributes:
|
70
|
+
units (str): The units of measurement for the parameter
|
71
|
+
samples (int): The number of samples to take from the available strings
|
72
|
+
"""
|
45
73
|
|
46
74
|
def __init__(
|
47
75
|
self,
|
@@ -61,11 +89,21 @@ class StringSweep(SweepSelector):
|
|
61
89
|
|
62
90
|
|
63
91
|
class EnumSweep(SweepSelector):
|
64
|
-
"""A class
|
92
|
+
"""A class representing a parameter sweep for enum values.
|
93
|
+
|
94
|
+
This class extends SweepSelector to provide parameter sweeping capabilities
|
95
|
+
specifically for enumeration types, supporting both enum types and lists of enum values.
|
96
|
+
|
97
|
+
Attributes:
|
98
|
+
units (str): The units of measurement for the parameter
|
99
|
+
samples (int): The number of samples to take from the available enum values
|
100
|
+
"""
|
65
101
|
|
66
102
|
__slots__ = shared_slots
|
67
103
|
|
68
|
-
def __init__(
|
104
|
+
def __init__(
|
105
|
+
self, enum_type: Enum | List[Enum], units: str = "ul", samples: int = None, **params
|
106
|
+
):
|
69
107
|
# The enum can either be an Enum type or a list of enums
|
70
108
|
list_of_enums = isinstance(enum_type, list)
|
71
109
|
selector_list = enum_type if list_of_enums else list(enum_type)
|
@@ -82,11 +120,23 @@ class EnumSweep(SweepSelector):
|
|
82
120
|
|
83
121
|
|
84
122
|
class IntSweep(Integer, SweepBase):
|
85
|
-
"""A class
|
123
|
+
"""A class representing a parameter sweep for integer values.
|
124
|
+
|
125
|
+
This class extends both Integer and SweepBase to provide parameter sweeping capabilities
|
126
|
+
specifically for integer values within specified bounds or with custom sample values.
|
127
|
+
|
128
|
+
Attributes:
|
129
|
+
units (str): The units of measurement for the parameter
|
130
|
+
samples (int): The number of samples to take from the range
|
131
|
+
sample_values (List[int], optional): Specific integer values to use as samples instead of
|
132
|
+
generating them from bounds. If provided, overrides the samples parameter.
|
133
|
+
"""
|
86
134
|
|
87
135
|
__slots__ = shared_slots + ["sample_values"]
|
88
136
|
|
89
|
-
def __init__(
|
137
|
+
def __init__(
|
138
|
+
self, units: str = "ul", samples: int = None, sample_values: List[int] = None, **params
|
139
|
+
):
|
90
140
|
SweepBase.__init__(self)
|
91
141
|
Integer.__init__(self, **params)
|
92
142
|
|
@@ -107,7 +157,14 @@ class IntSweep(Integer, SweepBase):
|
|
107
157
|
self.default = sample_values[0]
|
108
158
|
|
109
159
|
def values(self) -> List[int]:
|
110
|
-
"""
|
160
|
+
"""Return all the values for the parameter sweep.
|
161
|
+
|
162
|
+
If sample_values is provided, returns those values. Otherwise generates values
|
163
|
+
within the specified bounds.
|
164
|
+
|
165
|
+
Returns:
|
166
|
+
List[int]: A list of integer values to sweep through
|
167
|
+
"""
|
111
168
|
sample_values = (
|
112
169
|
self.sample_values
|
113
170
|
if self.sample_values is not None
|
@@ -136,11 +193,29 @@ class IntSweep(Integer, SweepBase):
|
|
136
193
|
|
137
194
|
|
138
195
|
class FloatSweep(Number, SweepBase):
|
139
|
-
"""A class
|
196
|
+
"""A class representing a parameter sweep for floating point values.
|
197
|
+
|
198
|
+
This class extends both Number and SweepBase to provide parameter sweeping capabilities
|
199
|
+
specifically for floating point values within specified bounds or with custom sample values.
|
200
|
+
|
201
|
+
Attributes:
|
202
|
+
units (str): The units of measurement for the parameter
|
203
|
+
samples (int): The number of samples to take from the range
|
204
|
+
sample_values (List[float], optional): Specific float values to use as samples instead of
|
205
|
+
generating them from bounds. If provided, overrides the samples parameter.
|
206
|
+
step (float, optional): Step size between samples when generating values from bounds
|
207
|
+
"""
|
140
208
|
|
141
209
|
__slots__ = shared_slots + ["sample_values"]
|
142
210
|
|
143
|
-
def __init__(
|
211
|
+
def __init__(
|
212
|
+
self,
|
213
|
+
units: str = "ul",
|
214
|
+
samples: int = 10,
|
215
|
+
sample_values: List[float] = None,
|
216
|
+
step: float = None,
|
217
|
+
**params,
|
218
|
+
):
|
144
219
|
SweepBase.__init__(self)
|
145
220
|
Number.__init__(self, step=step, **params)
|
146
221
|
|
@@ -156,7 +231,14 @@ class FloatSweep(Number, SweepBase):
|
|
156
231
|
self.default = sample_values[0]
|
157
232
|
|
158
233
|
def values(self) -> List[float]:
|
159
|
-
"""
|
234
|
+
"""Return all the values for the parameter sweep.
|
235
|
+
|
236
|
+
If sample_values is provided, returns those values. Otherwise generates values
|
237
|
+
within the specified bounds, either using linspace (when step is None) or arange.
|
238
|
+
|
239
|
+
Returns:
|
240
|
+
List[float]: A list of float values to sweep through
|
241
|
+
"""
|
160
242
|
samps = self.samples
|
161
243
|
if self.sample_values is None:
|
162
244
|
if self.step is None:
|
@@ -166,7 +248,20 @@ class FloatSweep(Number, SweepBase):
|
|
166
248
|
return self.sample_values
|
167
249
|
|
168
250
|
|
169
|
-
def box(name, center, width):
|
251
|
+
def box(name: str, center: float, width: float) -> FloatSweep:
|
252
|
+
"""Create a FloatSweep parameter centered around a value with a given width.
|
253
|
+
|
254
|
+
This is a convenience function to create a bounded FloatSweep parameter with
|
255
|
+
bounds centered on a specific value, extending by the width in both directions.
|
256
|
+
|
257
|
+
Args:
|
258
|
+
name (str): The name of the parameter
|
259
|
+
center (float): The center value of the parameter
|
260
|
+
width (float): The distance from the center to the bounds in both directions
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
FloatSweep: A FloatSweep parameter with the specified name, default, and bounds
|
264
|
+
"""
|
170
265
|
var = FloatSweep(default=center, bounds=(center - width, center + width))
|
171
266
|
var.name = name
|
172
267
|
return var
|
@@ -195,6 +290,18 @@ def p(
|
|
195
290
|
return {"name": name, "values": values, "max_level": max_level, "samples": samples}
|
196
291
|
|
197
292
|
|
198
|
-
def with_level(arr: list, level) -> list:
|
293
|
+
def with_level(arr: list, level: int) -> list:
|
294
|
+
"""Apply level-based sampling to a list of values.
|
295
|
+
|
296
|
+
This function uses an IntSweep with the provided values and applies level-based
|
297
|
+
sampling to it, returning the resulting values.
|
298
|
+
|
299
|
+
Args:
|
300
|
+
arr (list): List of values to sample from
|
301
|
+
level (int): The sampling level to apply (higher levels provide more samples)
|
302
|
+
|
303
|
+
Returns:
|
304
|
+
list: The level-sampled values
|
305
|
+
"""
|
199
306
|
return IntSweep(sample_values=arr).with_level(level).values()
|
200
307
|
# return tmp.with_sample_values(arr).with_level(level).values()
|
bencher/video_writer.py
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
import numpy as np
|
2
2
|
import moviepy.video.io.ImageSequenceClip
|
3
|
+
import moviepy.video.io.VideoFileClip
|
3
4
|
from pathlib import Path
|
4
5
|
from .utils import gen_video_path, gen_image_path
|
5
|
-
|
6
|
-
import moviepy
|
7
6
|
from PIL import Image, ImageDraw
|
8
7
|
|
9
8
|
|
@@ -46,6 +45,16 @@ class VideoWriter:
|
|
46
45
|
new_img.paste(image, (0, padding))
|
47
46
|
return new_img
|
48
47
|
|
48
|
+
@staticmethod
|
49
|
+
def convert_to_compatible_format(video_path: str) -> str:
|
50
|
+
new_path = Path(video_path)
|
51
|
+
new_path = new_path.with_name(f"{new_path.stem}_fixed{new_path.suffix}").as_posix()
|
52
|
+
vw = VideoWriter()
|
53
|
+
vw.filename = new_path
|
54
|
+
with moviepy.video.io.VideoFileClip.VideoFileClip(video_path) as vid:
|
55
|
+
vw.write_video_raw(vid)
|
56
|
+
return new_path
|
57
|
+
|
49
58
|
def write_video_raw(self, video_clip: moviepy.video.VideoClip, fps: int = 30) -> str:
|
50
59
|
video_clip.write_videofile(
|
51
60
|
self.filename,
|
@@ -59,6 +68,33 @@ class VideoWriter:
|
|
59
68
|
video_clip.close()
|
60
69
|
return self.filename
|
61
70
|
|
71
|
+
@staticmethod
|
72
|
+
def extract_frame(video_path: str, time: float = None, output_path: str = None) -> str:
|
73
|
+
"""Extract a frame from a video at a specific time.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
video_path: Path to the video file
|
77
|
+
time: Time in seconds to extract frame. If None, uses last frame
|
78
|
+
output_path: Optional path where to save the image. If None, uses video name with _frame.png
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
str: Path to the saved PNG image
|
82
|
+
"""
|
83
|
+
if output_path is None:
|
84
|
+
output_path = (
|
85
|
+
Path(video_path).with_stem(f"{Path(video_path).stem}_frame").with_suffix(".png")
|
86
|
+
)
|
87
|
+
else:
|
88
|
+
output_path = Path(output_path)
|
89
|
+
|
90
|
+
with moviepy.video.io.VideoFileClip.VideoFileClip(video_path) as video:
|
91
|
+
frame_time = time if time is not None else video.duration - 2.0 / video.fps
|
92
|
+
frame_time = max(frame_time, 0)
|
93
|
+
frame = video.get_frame(frame_time)
|
94
|
+
Image.fromarray(frame).save(output_path)
|
95
|
+
|
96
|
+
return output_path.as_posix()
|
97
|
+
|
62
98
|
|
63
99
|
def add_image(np_array: np.ndarray, name: str = "img") -> str:
|
64
100
|
"""Creates a file on disk from a numpy array and returns the created image path"""
|