holobench 1.19.0__py2.py3-none-any.whl → 1.30.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bencher/__init__.py +12 -1
- bencher/bench_report.py +6 -109
- bencher/bench_runner.py +1 -1
- bencher/bencher.py +103 -57
- bencher/example/benchmark_data.py +0 -4
- bencher/example/example_composable_container.py +106 -0
- bencher/example/example_composable_container2.py +160 -0
- bencher/example/example_consts.py +39 -0
- bencher/example/example_custom_sweep2.py +42 -0
- bencher/example/example_dataframe.py +48 -0
- bencher/example/example_image.py +32 -17
- bencher/example/example_image1.py +81 -0
- bencher/example/example_levels2.py +37 -0
- bencher/example/example_simple_float.py +15 -25
- bencher/example/example_simple_float2d.py +29 -0
- bencher/example/example_strings.py +3 -2
- bencher/example/example_video.py +2 -11
- bencher/example/meta/example_meta.py +2 -2
- bencher/example/meta/example_meta_cat.py +2 -2
- bencher/example/meta/example_meta_float.py +1 -1
- bencher/example/meta/example_meta_levels.py +2 -2
- bencher/optuna_conversions.py +3 -2
- bencher/plotting/plt_cnt_cfg.py +1 -0
- bencher/results/bench_result.py +3 -1
- bencher/results/bench_result_base.py +58 -8
- bencher/results/composable_container/composable_container_base.py +25 -12
- bencher/results/composable_container/composable_container_dataframe.py +52 -0
- bencher/results/composable_container/composable_container_panel.py +17 -18
- bencher/results/composable_container/composable_container_video.py +163 -55
- bencher/results/dataset_result.py +227 -0
- bencher/results/holoview_result.py +15 -7
- bencher/results/optuna_result.py +4 -3
- bencher/results/panel_result.py +1 -1
- bencher/results/video_summary.py +104 -99
- bencher/utils.py +28 -2
- bencher/variables/__init__.py +0 -0
- bencher/variables/inputs.py +24 -1
- bencher/variables/parametrised_sweep.py +6 -4
- bencher/variables/results.py +29 -1
- bencher/variables/time.py +22 -0
- bencher/video_writer.py +20 -74
- {holobench-1.19.0.dist-info → holobench-1.30.0.dist-info}/METADATA +77 -35
- {holobench-1.19.0.dist-info → holobench-1.30.0.dist-info}/RECORD +46 -33
- {holobench-1.19.0.dist-info → holobench-1.30.0.dist-info}/WHEEL +1 -1
- holobench-1.30.0.dist-info/licenses/LICENSE +21 -0
- resource/bencher +0 -0
@@ -11,8 +11,8 @@ def example_meta_levels(
|
|
11
11
|
title="Using Levels to define sample density",
|
12
12
|
description="Sample levels let you perform parameter sweeps without having to decide how many samples to take when defining the class. If you perform a sweep at level 2, then all the points are reused when sampling at level 3. The higher levels reuse the points from lower levels to avoid having to recompute potentially expensive samples. The other advantage is that it enables a workflow where you can quickly see the results of the sweep at a low resolution to sense check the code, and then run it at a high level to get the fidelity you want. When calling a sweep at a high level, you can publish the intermediate lower level results as the computiation continues so that you can track the progress of the computation and end the sweep early when you have sufficient resolution",
|
13
13
|
input_vars=[
|
14
|
-
|
15
|
-
|
14
|
+
bch.p("float_vars", [1, 2]),
|
15
|
+
bch.p("level", [2, 3, 4]),
|
16
16
|
],
|
17
17
|
const_vars=[
|
18
18
|
BenchMeta.param.categorical_vars.with_const(0),
|
bencher/optuna_conversions.py
CHANGED
@@ -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}```")
|
bencher/plotting/plt_cnt_cfg.py
CHANGED
@@ -47,6 +47,7 @@ class PltCntCfg(param.Parameterized):
|
|
47
47
|
plt_cnt_cfg.float_vars = []
|
48
48
|
|
49
49
|
for iv in bench_cfg.input_vars:
|
50
|
+
type_allocated = False
|
50
51
|
if isinstance(iv, (IntSweep, FloatSweep, TimeSnapshot)):
|
51
52
|
# if "IntSweep" in typestr or "FloatSweep" in typestr:
|
52
53
|
plt_cnt_cfg.float_vars.append(iv)
|
bencher/results/bench_result.py
CHANGED
@@ -7,15 +7,17 @@ from bencher.results.video_summary import VideoSummaryResult
|
|
7
7
|
from bencher.results.panel_result import PanelResult
|
8
8
|
from bencher.results.plotly_result import PlotlyResult
|
9
9
|
from bencher.results.holoview_result import HoloviewResult
|
10
|
+
from bencher.results.dataset_result import DataSetResult
|
10
11
|
from bencher.utils import listify
|
11
12
|
|
12
13
|
|
13
|
-
class BenchResult(PlotlyResult, HoloviewResult, VideoSummaryResult):
|
14
|
+
class BenchResult(PlotlyResult, HoloviewResult, VideoSummaryResult, DataSetResult):
|
14
15
|
"""Contains the results of the benchmark and has methods to cast the results to various datatypes and graphical representations"""
|
15
16
|
|
16
17
|
def __init__(self, bench_cfg) -> None:
|
17
18
|
PlotlyResult.__init__(self, bench_cfg)
|
18
19
|
HoloviewResult.__init__(self, bench_cfg)
|
20
|
+
# DataSetResult.__init__(self.bench_cfg)
|
19
21
|
|
20
22
|
@staticmethod
|
21
23
|
def default_plot_callbacks():
|
@@ -19,9 +19,7 @@ from bencher.variables.results import ResultVar
|
|
19
19
|
from bencher.plotting.plot_filter import VarRange, PlotFilter
|
20
20
|
from bencher.utils import listify
|
21
21
|
|
22
|
-
from bencher.variables.results import
|
23
|
-
ResultReference,
|
24
|
-
)
|
22
|
+
from bencher.variables.results import ResultReference, ResultDataSet
|
25
23
|
|
26
24
|
from bencher.results.composable_container.composable_container_panel import ComposableContainerPanel
|
27
25
|
|
@@ -242,6 +240,46 @@ class BenchResultBase(OptunaResult):
|
|
242
240
|
row.append(plot_callback(rv))
|
243
241
|
return row.get()
|
244
242
|
|
243
|
+
@staticmethod
|
244
|
+
def zip_results1D(args): # pragma: no cover
|
245
|
+
first_el = [a[0] for a in args]
|
246
|
+
out = pn.Column()
|
247
|
+
for a in zip(*first_el):
|
248
|
+
row = pn.Row()
|
249
|
+
row.append(a[0])
|
250
|
+
for a1 in range(1, len(a[1])):
|
251
|
+
row.append(a[a1][1])
|
252
|
+
out.append(row)
|
253
|
+
return out
|
254
|
+
|
255
|
+
@staticmethod
|
256
|
+
def zip_results1D1(panel_list): # pragma: no cover
|
257
|
+
container_args = {"styles": {}}
|
258
|
+
container_args["styles"]["border-bottom"] = f"{2}px solid grey"
|
259
|
+
print(panel_list)
|
260
|
+
out = pn.Column()
|
261
|
+
for a in zip(*panel_list):
|
262
|
+
row = pn.Row(**container_args)
|
263
|
+
row.append(a[0][0])
|
264
|
+
for a1 in range(0, len(a)):
|
265
|
+
row.append(a[a1][1])
|
266
|
+
out.append(row)
|
267
|
+
return out
|
268
|
+
|
269
|
+
@staticmethod
|
270
|
+
def zip_results1D2(panel_list): # pragma: no cover
|
271
|
+
if panel_list is not None:
|
272
|
+
print(panel_list)
|
273
|
+
primary = panel_list[0]
|
274
|
+
secondary = panel_list[1:]
|
275
|
+
for i in range(len(primary)):
|
276
|
+
print(type(primary[i]))
|
277
|
+
if isinstance(primary[i], (pn.Column, pn.Row)):
|
278
|
+
for j in range(len(secondary)):
|
279
|
+
primary[i].append(secondary[j][i][1])
|
280
|
+
return primary
|
281
|
+
return panel_list
|
282
|
+
|
245
283
|
def map_plot_panes(
|
246
284
|
self,
|
247
285
|
plot_callback: callable,
|
@@ -250,6 +288,7 @@ class BenchResultBase(OptunaResult):
|
|
250
288
|
result_var: ResultVar = None,
|
251
289
|
result_types=None,
|
252
290
|
pane_collection: pn.pane = None,
|
291
|
+
zip_results=False,
|
253
292
|
**kwargs,
|
254
293
|
) -> Optional[pn.Row]:
|
255
294
|
if hv_dataset is None:
|
@@ -271,6 +310,9 @@ class BenchResultBase(OptunaResult):
|
|
271
310
|
target_dimension=target_dimension,
|
272
311
|
)
|
273
312
|
)
|
313
|
+
|
314
|
+
if zip_results:
|
315
|
+
return self.zip_results1D2(row.get())
|
274
316
|
return row.get()
|
275
317
|
|
276
318
|
def filter(
|
@@ -365,7 +407,7 @@ class BenchResultBase(OptunaResult):
|
|
365
407
|
sliced = dataset.isel({selected_dim: i})
|
366
408
|
label_val = sliced.coords[selected_dim].values.item()
|
367
409
|
inner_container = ComposableContainerPanel(
|
368
|
-
outer_container.name,
|
410
|
+
name=outer_container.name,
|
369
411
|
width=num_dims - target_dimension,
|
370
412
|
var_name=selected_dim,
|
371
413
|
var_value=label_val,
|
@@ -405,15 +447,23 @@ class BenchResultBase(OptunaResult):
|
|
405
447
|
return da_ds.values.squeeze().item()
|
406
448
|
return da.expand_dims(dim).values[0]
|
407
449
|
|
408
|
-
def ds_to_container(
|
450
|
+
def ds_to_container( # pylint: disable=too-many-return-statements
|
409
451
|
self, dataset: xr.Dataset, result_var: Parameter, container, **kwargs
|
410
452
|
) -> Any:
|
411
453
|
val = self.zero_dim_da_to_val(dataset[result_var.name])
|
454
|
+
if isinstance(result_var, ResultDataSet):
|
455
|
+
ref = self.dataset_list[val]
|
456
|
+
if ref is not None:
|
457
|
+
if container is not None:
|
458
|
+
return container(ref.obj)
|
459
|
+
return ref.obj
|
460
|
+
return None
|
412
461
|
if isinstance(result_var, ResultReference):
|
413
462
|
ref = self.object_index[val]
|
414
|
-
|
415
|
-
|
416
|
-
|
463
|
+
if ref is not None:
|
464
|
+
val = ref.obj
|
465
|
+
if ref.container is not None:
|
466
|
+
return ref.container(val, **kwargs)
|
417
467
|
if container is not None:
|
418
468
|
return container(val, styles={"background": "white"}, **kwargs)
|
419
469
|
try:
|
@@ -1,18 +1,38 @@
|
|
1
|
-
from enum import
|
2
|
-
from typing import Any
|
1
|
+
from enum import auto
|
2
|
+
from typing import Any, List
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from strenum import StrEnum
|
3
5
|
from bencher.results.float_formatter import FormatFloat
|
4
6
|
|
5
7
|
|
6
8
|
# TODO enable these options
|
7
|
-
class ComposeType(
|
9
|
+
class ComposeType(StrEnum):
|
8
10
|
right = auto() # append the container to the right (creates a row)
|
9
11
|
down = auto() # append the container below (creates a column)
|
10
|
-
overlay = auto() # overlay on top of the current container (alpha blending)
|
11
12
|
sequence = auto() # display the container after (in time)
|
13
|
+
overlay = auto() # overlay on top of the current container (alpha blending)
|
12
14
|
|
15
|
+
def flip(self):
|
16
|
+
match self:
|
17
|
+
case ComposeType.right:
|
18
|
+
return ComposeType.down
|
19
|
+
case ComposeType.down:
|
20
|
+
return ComposeType.right
|
21
|
+
case _:
|
22
|
+
raise RuntimeError("cannot flip this type")
|
23
|
+
|
24
|
+
@staticmethod
|
25
|
+
def from_horizontal(horizontal: bool):
|
26
|
+
return ComposeType.right if horizontal else ComposeType.down
|
13
27
|
|
28
|
+
|
29
|
+
@dataclass(kw_only=True)
|
14
30
|
class ComposableContainerBase:
|
15
|
-
"""A base class for renderer backends. A composable
|
31
|
+
"""A base class for renderer backends. A composable renderer"""
|
32
|
+
|
33
|
+
compose_method: ComposeType = ComposeType.right
|
34
|
+
container: List[Any] = field(default_factory=list)
|
35
|
+
label_len: int = 0
|
16
36
|
|
17
37
|
@staticmethod
|
18
38
|
def label_formatter(var_name: str, var_value: int | float | str) -> str:
|
@@ -36,13 +56,6 @@ class ComposableContainerBase:
|
|
36
56
|
return f"{var_value}"
|
37
57
|
return None
|
38
58
|
|
39
|
-
def __init__(
|
40
|
-
self, horizontal: bool = True, compose_method: ComposeType = ComposeType.right
|
41
|
-
) -> None:
|
42
|
-
self.horizontal: bool = horizontal
|
43
|
-
self.compose_method = compose_method
|
44
|
-
self.container = []
|
45
|
-
|
46
59
|
def append(self, obj: Any) -> None:
|
47
60
|
"""Add an object to the container. The relationship between the objects is defined by the ComposeType
|
48
61
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
import panel as pn
|
3
|
+
import xarray as xr
|
4
|
+
from bencher.results.composable_container.composable_container_base import ComposableContainerBase
|
5
|
+
from bencher.results.composable_container.composable_container_base import ComposeType
|
6
|
+
|
7
|
+
|
8
|
+
@dataclass(kw_only=True)
|
9
|
+
class ComposableContainerDataset(ComposableContainerBase):
|
10
|
+
name: str = None
|
11
|
+
var_name: str = None
|
12
|
+
var_value: str = None
|
13
|
+
width: int = None
|
14
|
+
background_col: str = None
|
15
|
+
horizontal: bool = True
|
16
|
+
|
17
|
+
def __post_init__(
|
18
|
+
self,
|
19
|
+
) -> None:
|
20
|
+
container_args = {
|
21
|
+
"name": self.name,
|
22
|
+
"styles": {},
|
23
|
+
}
|
24
|
+
|
25
|
+
if self.width is not None:
|
26
|
+
container_args["styles"]["border-bottom"] = f"{self.width}px solid grey"
|
27
|
+
if self.background_col is not None:
|
28
|
+
container_args["styles"]["background"] = self.background_col
|
29
|
+
|
30
|
+
if self.horizontal:
|
31
|
+
self.container = pn.Column(**container_args)
|
32
|
+
align = ("center", "center")
|
33
|
+
else:
|
34
|
+
self.container = pn.Row(**container_args)
|
35
|
+
align = ("end", "center")
|
36
|
+
|
37
|
+
label = self.label_formatter(self.var_name, self.var_value)
|
38
|
+
if label is not None:
|
39
|
+
self.label_len = len(label)
|
40
|
+
side = pn.pane.Markdown(label, align=align)
|
41
|
+
self.append(side)
|
42
|
+
|
43
|
+
def render(self, **kwargs): # pylint: disable=unused-argument
|
44
|
+
match self.compose_method:
|
45
|
+
case ComposeType.right:
|
46
|
+
return xr.concat(self.container, 0)
|
47
|
+
case ComposeType.down:
|
48
|
+
return xr.concat(self.container, 1)
|
49
|
+
case ComposeType.sequence:
|
50
|
+
return xr.concat(self.container, 2)
|
51
|
+
# case ComposeType.overlay:
|
52
|
+
# return xr.Dataset.mean()
|
@@ -1,39 +1,38 @@
|
|
1
1
|
import panel as pn
|
2
2
|
from bencher.results.composable_container.composable_container_base import ComposableContainerBase
|
3
|
+
from dataclasses import dataclass
|
3
4
|
|
4
5
|
|
6
|
+
@dataclass(kw_only=True)
|
5
7
|
class ComposableContainerPanel(ComposableContainerBase):
|
6
|
-
|
8
|
+
name: str = None
|
9
|
+
var_name: str = None
|
10
|
+
var_value: str = None
|
11
|
+
width: int = None
|
12
|
+
background_col: str = None
|
13
|
+
horizontal: bool = True
|
14
|
+
|
15
|
+
def __post_init__(
|
7
16
|
self,
|
8
|
-
name: str = None,
|
9
|
-
var_name: str = None,
|
10
|
-
var_value: str = None,
|
11
|
-
width: int = None,
|
12
|
-
background_col: str = None,
|
13
|
-
horizontal: bool = True,
|
14
17
|
) -> None:
|
15
|
-
super().__init__(horizontal)
|
16
|
-
|
17
18
|
container_args = {
|
18
|
-
"name": name,
|
19
|
+
"name": self.name,
|
19
20
|
"styles": {},
|
20
21
|
}
|
21
22
|
|
22
|
-
self.
|
23
|
-
|
24
|
-
if
|
25
|
-
container_args["styles"]["
|
26
|
-
if background_col is not None:
|
27
|
-
container_args["styles"]["background"] = background_col
|
23
|
+
if self.width is not None:
|
24
|
+
container_args["styles"]["border-bottom"] = f"{self.width}px solid grey"
|
25
|
+
if self.background_col is not None:
|
26
|
+
container_args["styles"]["background"] = self.background_col
|
28
27
|
|
29
|
-
if horizontal:
|
28
|
+
if self.horizontal:
|
30
29
|
self.container = pn.Column(**container_args)
|
31
30
|
align = ("center", "center")
|
32
31
|
else:
|
33
32
|
self.container = pn.Row(**container_args)
|
34
33
|
align = ("end", "center")
|
35
34
|
|
36
|
-
label = self.label_formatter(var_name, var_value)
|
35
|
+
label = self.label_formatter(self.var_name, self.var_value)
|
37
36
|
if label is not None:
|
38
37
|
self.label_len = len(label)
|
39
38
|
side = pn.pane.Markdown(label, align=align)
|
@@ -1,76 +1,184 @@
|
|
1
|
+
from __future__ import annotations
|
1
2
|
import numpy as np
|
3
|
+
from copy import deepcopy
|
4
|
+
from pathlib import Path
|
5
|
+
from dataclasses import dataclass
|
2
6
|
from moviepy.editor import (
|
3
7
|
ImageClip,
|
4
8
|
CompositeVideoClip,
|
5
9
|
clips_array,
|
6
10
|
concatenate_videoclips,
|
7
11
|
VideoClip,
|
12
|
+
VideoFileClip,
|
8
13
|
)
|
14
|
+
from moviepy.video.fx.margin import margin
|
9
15
|
|
10
|
-
from bencher.results.composable_container.composable_container_base import
|
16
|
+
from bencher.results.composable_container.composable_container_base import (
|
17
|
+
ComposableContainerBase,
|
18
|
+
ComposeType,
|
19
|
+
)
|
11
20
|
from bencher.video_writer import VideoWriter
|
12
21
|
|
13
22
|
|
23
|
+
@dataclass()
|
24
|
+
class RenderCfg:
|
25
|
+
compose_method: ComposeType = ComposeType.sequence
|
26
|
+
var_name: str = None
|
27
|
+
var_value: str = None
|
28
|
+
background_col: tuple[int, int, int] = (255, 255, 255)
|
29
|
+
duration: float = 10.0
|
30
|
+
duration_target: bool = True
|
31
|
+
min_frame_duration: float = 1.0 / 30
|
32
|
+
max_frame_duration: float = 2.0
|
33
|
+
margin: int = 0
|
34
|
+
|
35
|
+
|
36
|
+
@dataclass
|
14
37
|
class ComposableContainerVideo(ComposableContainerBase):
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
def append(self, obj: VideoClip | ImageClip | str | np.ndarray) -> None:
|
39
|
+
"""Appends an image or video to the container
|
40
|
+
|
41
|
+
Args:
|
42
|
+
obj (VideoClip | ImageClip | str | np.ndarray): Any representation of an image or video
|
43
|
+
|
44
|
+
Raises:
|
45
|
+
RuntimeWarning: if file format is not recognised
|
46
|
+
"""
|
47
|
+
|
48
|
+
# print(f"append obj: {type(obj)}, {obj}")
|
49
|
+
if obj is not None:
|
50
|
+
if isinstance(obj, VideoClip):
|
51
|
+
self.container.append(obj)
|
52
|
+
elif isinstance(obj, ComposableContainerVideo):
|
53
|
+
self.container.append(obj.render())
|
54
|
+
elif isinstance(obj, np.ndarray):
|
55
|
+
self.container.append(ImageClip(obj))
|
56
|
+
else:
|
57
|
+
path = Path(obj)
|
58
|
+
extension = str.lower(path.suffix)
|
59
|
+
if extension in [".jpg", ".jepg", ".png"]:
|
60
|
+
self.container.append(ImageClip(obj))
|
61
|
+
elif extension in [".mpeg", ".mpg", ".mp4", ".webm"]:
|
62
|
+
# print(obj)
|
63
|
+
self.container.append(VideoFileClip(obj))
|
64
|
+
else:
|
65
|
+
raise RuntimeWarning(f"unsupported filetype {extension}")
|
66
|
+
else:
|
67
|
+
raise RuntimeWarning("No data passed to ComposableContainerVideo.append()")
|
68
|
+
|
69
|
+
def calculate_duration(self, frames, render_cfg: RenderCfg):
|
70
|
+
if render_cfg.duration_target:
|
71
|
+
# calculate duration based on fps constraints
|
72
|
+
duration = 10.0 if render_cfg.duration is None else render_cfg.duration
|
73
|
+
frame_duration = duration / frames
|
74
|
+
if render_cfg.min_frame_duration is not None:
|
75
|
+
frame_duration = max(frame_duration, render_cfg.min_frame_duration)
|
76
|
+
if render_cfg.max_frame_duration is not None:
|
77
|
+
frame_duration = min(frame_duration, render_cfg.max_frame_duration)
|
78
|
+
duration = frame_duration * frames
|
44
79
|
else:
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
80
|
+
duration = render_cfg.duration
|
81
|
+
frame_duration = duration / float(frames)
|
82
|
+
|
83
|
+
print("max_frame_duration", render_cfg.max_frame_duration)
|
84
|
+
print("DURATION", duration)
|
85
|
+
|
86
|
+
return duration, frame_duration
|
87
|
+
|
88
|
+
def render(self, render_cfg: RenderCfg = None, **kwargs) -> CompositeVideoClip:
|
89
|
+
"""Composes the images/videos into a single image/video based on the type of compose method
|
50
90
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
91
|
+
Args:
|
92
|
+
compose_method (ComposeType, optional): optionally override the default compose type. Defaults to None.
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
CompositeVideoClip: A composite video clip containing the images/videos added via append()
|
96
|
+
"""
|
97
|
+
if render_cfg is None:
|
98
|
+
render_cfg = RenderCfg(**kwargs)
|
99
|
+
|
100
|
+
print("rc", render_cfg)
|
101
|
+
_, frame_duration = self.calculate_duration(float(len(self.container)), render_cfg)
|
102
|
+
out = None
|
103
|
+
print(f"using compose type{render_cfg.compose_method}")
|
104
|
+
max_duration = 0.0
|
55
105
|
|
56
106
|
for i in range(len(self.container)):
|
57
|
-
self.container[i].duration
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
107
|
+
if self.container[i].duration is None:
|
108
|
+
self.container[i].duration = frame_duration
|
109
|
+
max_duration = max(max_duration, self.container[i].duration)
|
110
|
+
match render_cfg.compose_method:
|
111
|
+
case ComposeType.right | ComposeType.down:
|
112
|
+
for i in range(len(self.container)):
|
113
|
+
self.container[i] = self.extend_clip(self.container[i], max_duration)
|
114
|
+
self.container[i] = margin(
|
115
|
+
self.container[i], top=render_cfg.margin, color=render_cfg.background_col
|
116
|
+
)
|
117
|
+
|
118
|
+
if render_cfg.compose_method == ComposeType.right:
|
119
|
+
clips = [self.container]
|
120
|
+
else:
|
121
|
+
clips = [[c] for c in self.container]
|
122
|
+
out = clips_array(clips, bg_color=render_cfg.background_col)
|
123
|
+
if out.duration is None:
|
124
|
+
out.duration = max_duration
|
125
|
+
case ComposeType.sequence:
|
126
|
+
out = concatenate_videoclips(
|
127
|
+
self.container, bg_color=render_cfg.background_col, method="compose"
|
128
|
+
)
|
129
|
+
# case ComposeType.overlay:
|
130
|
+
# for i in range(len(self.container)):
|
131
|
+
# self.container[i].alpha = 1./len(self.container)
|
132
|
+
# out = CompositeVideoClip(self.container, bg_color=render_args.background_col)
|
133
|
+
# out.duration = fps
|
134
|
+
case _:
|
135
|
+
raise RuntimeError("This compose type is not supported")
|
136
|
+
|
137
|
+
label = self.label_formatter(render_cfg.var_name, render_cfg.var_value)
|
138
|
+
if label is not None:
|
139
|
+
# print("adding label")
|
140
|
+
label = ImageClip(
|
141
|
+
np.array(VideoWriter.create_label(label, color=render_cfg.background_col))
|
72
142
|
)
|
143
|
+
label.duration = out.duration
|
144
|
+
label_compose = ComposeType.down
|
145
|
+
if render_cfg.compose_method == ComposeType.down:
|
146
|
+
label_compose = ComposeType.right
|
147
|
+
con2 = ComposableContainerVideo()
|
73
148
|
con2.append(label)
|
74
149
|
con2.append(out)
|
75
|
-
return con2.render(
|
150
|
+
return con2.render(
|
151
|
+
RenderCfg(
|
152
|
+
background_col=render_cfg.background_col,
|
153
|
+
compose_method=label_compose,
|
154
|
+
duration=out.duration,
|
155
|
+
duration_target=False, # want exact duration
|
156
|
+
)
|
157
|
+
)
|
76
158
|
return out
|
159
|
+
|
160
|
+
def to_video(
|
161
|
+
self,
|
162
|
+
render_args: RenderCfg = None,
|
163
|
+
) -> str:
|
164
|
+
"""Returns the composite video clip as a webm file path
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
str: webm filepath
|
168
|
+
"""
|
169
|
+
return VideoWriter().write_video_raw(self.render(render_args))
|
170
|
+
|
171
|
+
def deep(self):
|
172
|
+
return deepcopy(self)
|
173
|
+
|
174
|
+
def extend_clip(self, clip: VideoClip, desired_duration: float):
|
175
|
+
if clip.duration < desired_duration:
|
176
|
+
return concatenate_videoclips(
|
177
|
+
[
|
178
|
+
clip,
|
179
|
+
ImageClip(
|
180
|
+
clip.get_frame(clip.duration), duration=desired_duration - clip.duration
|
181
|
+
),
|
182
|
+
]
|
183
|
+
)
|
184
|
+
return clip
|