code-loader 1.0.196.dev0__tar.gz → 1.0.197__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.
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/PKG-INFO +1 -1
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/contract/datasetclasses.py +9 -1
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/contract/enums.py +1 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/contract/visualizer_classes.py +43 -1
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/inner_leap_binder/leapbinder.py +51 -3
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/inner_leap_binder/leapbinder_decorators.py +48 -14
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/leaploader.py +57 -17
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/utils.py +33 -22
- code_loader-1.0.197/code_loader/visualizers/default_visualizers.py +149 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/pyproject.toml +1 -1
- code_loader-1.0.196.dev0/code_loader/visualizers/default_visualizers.py +0 -85
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/LICENSE +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/README.md +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/__init__.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/contract/__init__.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/contract/exceptions.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/contract/mapping.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/contract/responsedataclasses.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/contract/sim_config.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/default_losses.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/default_metrics.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/__init__.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/api.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/cli_config_utils.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/client.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/epoch.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/experiment.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/experiment_context.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/types.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/utils.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/inner_leap_binder/__init__.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/leaploaderbase.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/mixpanel_tracker.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/plot_functions/__init__.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/plot_functions/plot_functions.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/plot_functions/visualize.py +0 -0
- {code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/visualizers/__init__.py +0 -0
|
@@ -59,7 +59,8 @@ class PreprocessResponse:
|
|
|
59
59
|
self.sample_ids = cast(List[SampleId], [i for i in range(self.length)])
|
|
60
60
|
self.sample_id_type = int
|
|
61
61
|
self._grouped = False
|
|
62
|
-
elif self.
|
|
62
|
+
elif self.sample_ids is not None:
|
|
63
|
+
given_length = self.length
|
|
63
64
|
self._grouped = len(self.sample_ids) > 0 and isinstance(self.sample_ids[0], list)
|
|
64
65
|
if self._grouped:
|
|
65
66
|
groups = cast(List[List[SampleId]], self.sample_ids)
|
|
@@ -68,6 +69,8 @@ class PreprocessResponse:
|
|
|
68
69
|
assert isinstance(group, list) and len(group) > 0, \
|
|
69
70
|
"Each group in a grouped PreprocessResponse must be a non-empty list."
|
|
70
71
|
self.sample_id_type = type(groups[0][0])
|
|
72
|
+
assert issubclass(self.sample_id_type, (str, int)), \
|
|
73
|
+
f"Sample id should be of type str or int. Got: {self.sample_id_type.__name__}"
|
|
71
74
|
for group in groups:
|
|
72
75
|
for sample_id in group:
|
|
73
76
|
assert isinstance(sample_id, self.sample_id_type), \
|
|
@@ -81,6 +84,11 @@ class PreprocessResponse:
|
|
|
81
84
|
if self.sample_id_type == str:
|
|
82
85
|
for sample_id in flat_ids:
|
|
83
86
|
assert isinstance(sample_id, str), f"Sample id should be of type str. Got: {type(sample_id)}"
|
|
87
|
+
# dataclasses.replace() re-invokes __init__ with both fields already populated
|
|
88
|
+
# (length previously derived from sample_ids); only reject a genuine mismatch.
|
|
89
|
+
if given_length is not None:
|
|
90
|
+
assert given_length == self.length, \
|
|
91
|
+
f"Inconsistent PreprocessResponse: length={given_length} but sample_ids implies {self.length}."
|
|
84
92
|
else:
|
|
85
93
|
raise Exception("length is deprecated, please use sample_ids instead.")
|
|
86
94
|
|
|
@@ -343,6 +343,47 @@ class LeapImageWithHeatmap:
|
|
|
343
343
|
'Number of heatmaps and labels must be equal')
|
|
344
344
|
|
|
345
345
|
|
|
346
|
+
@dataclass
|
|
347
|
+
class LeapAudio:
|
|
348
|
+
"""
|
|
349
|
+
Visualizer representing a playable audio sample for Tensorleap.
|
|
350
|
+
|
|
351
|
+
The user plays the sample in the right panel while a playhead tick runs
|
|
352
|
+
through `visual` — either a spectrogram (a LeapImage) or a time-series
|
|
353
|
+
waveform (a LeapGraph). Build one with `default_audio_visualizer`.
|
|
354
|
+
|
|
355
|
+
Attributes:
|
|
356
|
+
audio (npt.NDArray): Mono waveform samples, shaped [N]. Multi-channel input
|
|
357
|
+
should be mixed down before constructing LeapAudio.
|
|
358
|
+
sample_rate (int): Samples per second — needed to encode the clip and to map
|
|
359
|
+
the playhead position to time.
|
|
360
|
+
visual (Union[LeapImage, LeapGraph]): The thing you look at.
|
|
361
|
+
x_range (Optional[Tuple[float, float]]): Time extent (in seconds) that the
|
|
362
|
+
visual's horizontal axis spans, used to align the tick. Defaults to
|
|
363
|
+
(0, N / sample_rate).
|
|
364
|
+
type (LeapDataType): The data type, default is LeapDataType.Audio.
|
|
365
|
+
"""
|
|
366
|
+
audio: npt.NDArray
|
|
367
|
+
sample_rate: int
|
|
368
|
+
visual: Union[LeapImage, LeapGraph]
|
|
369
|
+
x_range: Optional[Tuple[float, float]] = None
|
|
370
|
+
type: LeapDataType = LeapDataType.Audio
|
|
371
|
+
|
|
372
|
+
def __post_init__(self) -> None:
|
|
373
|
+
validate_type(self.type, LeapDataType.Audio)
|
|
374
|
+
validate_type(type(self.audio), np.ndarray)
|
|
375
|
+
validate_type(len(self.audio.shape), 1, 'Audio must be mono, of shape 1 [N]')
|
|
376
|
+
validate_type(type(self.visual), [LeapImage, LeapGraph],
|
|
377
|
+
'Audio visual must be a LeapImage (spectrogram) or LeapGraph (waveform)')
|
|
378
|
+
if not isinstance(self.sample_rate, int) or self.sample_rate <= 0:
|
|
379
|
+
raise LeapValidationError(f'sample_rate must be a positive int, got {self.sample_rate}')
|
|
380
|
+
if self.x_range is not None:
|
|
381
|
+
if len(self.x_range) != 2:
|
|
382
|
+
raise LeapValidationError('x_range must be a tuple of length 2')
|
|
383
|
+
validate_type(type(self.x_range[0]), [float, int], 'x_range must be a tuple of floats or integers')
|
|
384
|
+
validate_type(type(self.x_range[1]), [float, int], 'x_range must be a tuple of floats or integers')
|
|
385
|
+
|
|
386
|
+
|
|
346
387
|
map_leap_data_type_to_visualizer_class = {
|
|
347
388
|
LeapDataType.Image.value: LeapImage,
|
|
348
389
|
LeapDataType.Graph.value: LeapGraph,
|
|
@@ -352,5 +393,6 @@ map_leap_data_type_to_visualizer_class = {
|
|
|
352
393
|
LeapDataType.ImageMask.value: LeapImageMask,
|
|
353
394
|
LeapDataType.TextMask.value: LeapTextMask,
|
|
354
395
|
LeapDataType.ImageWithBBox.value: LeapImageWithBBox,
|
|
355
|
-
LeapDataType.ImageWithHeatmap.value: LeapImageWithHeatmap
|
|
396
|
+
LeapDataType.ImageWithHeatmap.value: LeapImageWithHeatmap,
|
|
397
|
+
LeapDataType.Audio.value: LeapAudio
|
|
356
398
|
}
|
{code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/inner_leap_binder/leapbinder.py
RENAMED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
|
|
2
|
+
import builtins
|
|
2
3
|
import inspect
|
|
3
|
-
|
|
4
|
+
import os
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from typing import Callable, List, Optional, Dict, Any, Type, Union, get_args, cast, Iterator, Set
|
|
4
7
|
|
|
5
8
|
import numpy as np
|
|
6
9
|
import numpy.typing as npt
|
|
@@ -16,7 +19,8 @@ from code_loader.contract.datasetclasses import SectionCallableInterface, InputH
|
|
|
16
19
|
SimulationHandler, _simulation_context, AutoregressiveStepHandler, AutoregressiveStepCallableInterface, \
|
|
17
20
|
AUTOREGRESSIVE_LATENT_SPACE_AGGREGATIONS, AUTOREGRESSIVE_IMPLICIT_ARG_NAMES, \
|
|
18
21
|
AutoregressiveMetricHandler, AutoregressiveLossHandler, AutoregressiveVisualizerHandler
|
|
19
|
-
from code_loader.contract.enums import LeapDataType, DataStateEnum, DataStateType, MetricDirection, DatasetMetadataType
|
|
22
|
+
from code_loader.contract.enums import LeapDataType, DataStateEnum, DataStateType, MetricDirection, DatasetMetadataType, \
|
|
23
|
+
TestingSectionEnum
|
|
20
24
|
from code_loader.contract.mapping import NodeConnection, NodeMapping, NodeMappingType
|
|
21
25
|
from code_loader.contract.responsedataclasses import DatasetTestResultPayload, LeapAnalysisConfiguration
|
|
22
26
|
from code_loader.contract.visualizer_classes import map_leap_data_type_to_visualizer_class
|
|
@@ -33,6 +37,25 @@ from code_loader.visualizers.default_visualizers import DefaultVisualizer, \
|
|
|
33
37
|
mapping_runtime_mode_env_var_mame = '__MAPPING_RUNTIME_MODE__'
|
|
34
38
|
|
|
35
39
|
|
|
40
|
+
@contextmanager
|
|
41
|
+
def _track_opened_files() -> Iterator[Set[str]]:
|
|
42
|
+
opened: Set[str] = set()
|
|
43
|
+
original_open = builtins.open
|
|
44
|
+
|
|
45
|
+
def tracking_open(file: Any, *args: Any, **kwargs: Any) -> Any:
|
|
46
|
+
mode = args[0] if args else kwargs.get('mode', 'r')
|
|
47
|
+
is_read_mode = isinstance(mode, str) and not any(c in mode for c in ('a', 'w', 'x'))
|
|
48
|
+
if is_read_mode and isinstance(file, (str, os.PathLike)):
|
|
49
|
+
opened.add(os.fspath(file))
|
|
50
|
+
return original_open(file, *args, **kwargs)
|
|
51
|
+
|
|
52
|
+
builtins.open = tracking_open
|
|
53
|
+
try:
|
|
54
|
+
yield opened
|
|
55
|
+
finally:
|
|
56
|
+
builtins.open = original_open
|
|
57
|
+
|
|
58
|
+
|
|
36
59
|
def _stringized_annotation_type_name(annotation: Any) -> Optional[str]:
|
|
37
60
|
"""Bare type name if ``annotation`` is a stringized annotation (from ``from __future__
|
|
38
61
|
import annotations`` or a quoted hint), else ``None``. code_loader inspects raw
|
|
@@ -744,15 +767,34 @@ class LeapBinder:
|
|
|
744
767
|
preprocess_response: PreprocessResponse, test_result: List[DatasetTestResultPayload],
|
|
745
768
|
dataset_base_handler: Union[DatasetBaseHandler, MetadataHandler], state: DataStateEnum) -> List[DatasetTestResultPayload]:
|
|
746
769
|
assert preprocess_response.sample_ids is not None
|
|
770
|
+
opened_files: Optional[Set[str]] = None
|
|
747
771
|
if preprocess_response.is_grouped:
|
|
748
772
|
# Grouped: probe with the first group, then reduce to a single sample's result so the
|
|
749
773
|
# recorded shape/type is per-sample (matching the flat case), not the (B, *) batch.
|
|
750
774
|
# (casts: sample_ids[0] is a group list here, and the grouped result is a per-sample list.)
|
|
751
775
|
group = preprocess_response.sample_ids[0]
|
|
752
|
-
|
|
776
|
+
with _track_opened_files() as opened_files:
|
|
777
|
+
raw_result = cast(Any, dataset_base_handler.function(cast(Any, group), preprocess_response))[0]
|
|
753
778
|
else:
|
|
754
779
|
raw_result = dataset_base_handler.function(
|
|
755
780
|
cast(Union[int, str], preprocess_response.sample_ids[0]), preprocess_response)
|
|
781
|
+
|
|
782
|
+
def _warn_if_group_spans_multiple_files(payloads: List[DatasetTestResultPayload]) -> None:
|
|
783
|
+
if opened_files is None or len(opened_files) <= 1:
|
|
784
|
+
return
|
|
785
|
+
shown = sorted(opened_files)[:5]
|
|
786
|
+
suffix = f" (+{len(opened_files) - 5} more)" if len(opened_files) > 5 else ""
|
|
787
|
+
warning = (
|
|
788
|
+
f"Encoding declared group 0 (size {len(cast(List[Any], group))}) for '{dataset_base_handler.name}' opened "
|
|
789
|
+
f"{len(opened_files)} distinct files: {shown}{suffix}. If this group is meant to be one "
|
|
790
|
+
f"physical source (e.g. one Parquet file), it may be grouped incorrectly - check your "
|
|
791
|
+
f"preprocess grouping logic. (Only files opened via Python's open() are tracked, so this "
|
|
792
|
+
f"can miss other I/O paths or be a false positive if grouping isn't meant to reflect file "
|
|
793
|
+
f"locality.)")
|
|
794
|
+
for payload in payloads:
|
|
795
|
+
existing = payload.display.get(TestingSectionEnum.Errors.name, '')
|
|
796
|
+
payload.display[TestingSectionEnum.Errors.name] = (existing + '\n' if existing else '') + warning
|
|
797
|
+
|
|
756
798
|
handler_type = 'metadata' if isinstance(dataset_base_handler, MetadataHandler) else None
|
|
757
799
|
if isinstance(dataset_base_handler, MetadataHandler):
|
|
758
800
|
if isinstance(raw_result, dict):
|
|
@@ -792,6 +834,7 @@ class LeapBinder:
|
|
|
792
834
|
else:
|
|
793
835
|
if raw_result is None:
|
|
794
836
|
if state != DataStateEnum.training:
|
|
837
|
+
_warn_if_group_spans_multiple_files(test_result)
|
|
795
838
|
return test_result
|
|
796
839
|
|
|
797
840
|
if dataset_base_handler.metadata_type is None:
|
|
@@ -813,6 +856,7 @@ class LeapBinder:
|
|
|
813
856
|
# setting shape in setup for all encoders
|
|
814
857
|
if isinstance(dataset_base_handler, (InputHandler, GroundTruthHandler)):
|
|
815
858
|
dataset_base_handler.shape = result_shape
|
|
859
|
+
_warn_if_group_spans_multiple_files(test_result)
|
|
816
860
|
return test_result
|
|
817
861
|
|
|
818
862
|
def check_handlers(self, preprocess_result: Dict[DataStateEnum, PreprocessResponse]) -> None:
|
|
@@ -848,6 +892,10 @@ class LeapBinder:
|
|
|
848
892
|
preprocess_response.tl_generated = True
|
|
849
893
|
if not preprocess_response.length or preprocess_response.length < 1:
|
|
850
894
|
raise Exception("Simulation '{}' returned PreprocessResponse with length < 1".format(sim.name))
|
|
895
|
+
if preprocess_response.is_grouped:
|
|
896
|
+
raise Exception(
|
|
897
|
+
"Simulation '{}' returned a grouped PreprocessResponse; simulations must "
|
|
898
|
+
"return a flat (non-grouped) PreprocessResponse.".format(sim.name))
|
|
851
899
|
preprocess_response.sample_ids = cast(List[Union[int, str]], [0])
|
|
852
900
|
sim_sample_ids = cast(List[Union[int, str]], preprocess_response.sample_ids)
|
|
853
901
|
for handler in self.setup_container.inputs:
|
|
@@ -29,7 +29,7 @@ from code_loader.contract.enums import MetricDirection, LeapDataType, DatasetMet
|
|
|
29
29
|
from code_loader import leap_binder, LeapLoader
|
|
30
30
|
from code_loader.contract.mapping import NodeMapping, NodeMappingType, NodeConnection
|
|
31
31
|
from code_loader.contract.visualizer_classes import LeapImage, LeapImageMask, LeapTextMask, LeapText, LeapGraph, \
|
|
32
|
-
LeapHorizontalBar, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapValidationError, \
|
|
32
|
+
LeapHorizontalBar, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapAudio, LeapValidationError, \
|
|
33
33
|
map_leap_data_type_to_visualizer_class
|
|
34
34
|
from code_loader.inner_leap_binder.leapbinder import mapping_runtime_mode_env_var_mame
|
|
35
35
|
from code_loader.mixpanel_tracker import clear_integration_events, AnalyticsEvent, emit_integration_event_once
|
|
@@ -686,10 +686,29 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
686
686
|
|
|
687
687
|
class ModelPlaceholder:
|
|
688
688
|
|
|
689
|
+
@staticmethod
|
|
690
|
+
def _reject_reused_input_source(elem, seen):
|
|
691
|
+
# A model input slot is wired by mutating elem.node_mapping.type, so the same
|
|
692
|
+
# source object at two positions silently overwrites the first slot. Each input
|
|
693
|
+
# encoder mints its own NodeMapping, so a collision means one source fed two
|
|
694
|
+
# slots — e.g. indexing a single grouped input, model([imgs[0], imgs[1]]), where
|
|
695
|
+
# imgs[i] returns the same node (a group is the batch dim of ONE input, not many
|
|
696
|
+
# inputs), or the flat model([img, img]). Reject it instead of mis-wiring.
|
|
697
|
+
if id(elem.node_mapping) in seen:
|
|
698
|
+
raise Exception(
|
|
699
|
+
"The same input source was passed to more than one model input slot. Each "
|
|
700
|
+
"model input must come from a distinct input encoder. Indexing a single "
|
|
701
|
+
"grouped input (e.g. model([imgs[0], imgs[1]])) feeds several samples of ONE "
|
|
702
|
+
"input into separate slots — a group is the batch dimension of one input, "
|
|
703
|
+
"not multiple inputs.")
|
|
704
|
+
seen.add(id(elem.node_mapping))
|
|
705
|
+
|
|
689
706
|
# keras interface
|
|
690
707
|
def __call__(self, arg):
|
|
691
708
|
if isinstance(arg, list):
|
|
709
|
+
seen: set = set()
|
|
692
710
|
for i, elem in enumerate(arg):
|
|
711
|
+
self._reject_reused_input_source(elem, seen)
|
|
693
712
|
elem.node_mapping.type = _safe_get_item(i)
|
|
694
713
|
else:
|
|
695
714
|
arg.node_mapping.type = NodeMappingType.Input0
|
|
@@ -703,7 +722,9 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
703
722
|
"use model.run(None, inputs) to return all outputs.")
|
|
704
723
|
assert isinstance(input_dict, dict), \
|
|
705
724
|
f'Expected input_dict to be a dict, got {type(input_dict)} instead.'
|
|
725
|
+
seen: set = set()
|
|
706
726
|
for i, (input_key, elem) in enumerate(input_dict.items()):
|
|
727
|
+
self._reject_reused_input_source(elem, seen)
|
|
707
728
|
if isinstance(input_key, NodeMappingType):
|
|
708
729
|
elem.node_mapping.type = input_key
|
|
709
730
|
else:
|
|
@@ -1522,7 +1543,8 @@ def tensorleap_custom_visualizer(name: str, visualizer_type: LeapDataType,
|
|
|
1522
1543
|
LeapDataType.HorizontalBar: LeapHorizontalBar,
|
|
1523
1544
|
LeapDataType.ImageWithBBox: LeapImageWithBBox,
|
|
1524
1545
|
LeapDataType.ImageWithHeatmap: LeapImageWithHeatmap,
|
|
1525
|
-
LeapDataType.Video: LeapVideo
|
|
1546
|
+
LeapDataType.Video: LeapVideo,
|
|
1547
|
+
LeapDataType.Audio: LeapAudio
|
|
1526
1548
|
}
|
|
1527
1549
|
validate_output_structure(result, func_name=user_function.__name__,
|
|
1528
1550
|
expected_type_name=result_type_map[visualizer_type])
|
|
@@ -2010,9 +2032,10 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
|
|
|
2010
2032
|
state returned by the previous call, and return the next step's full input dict together with
|
|
2011
2033
|
the updated state; returning (None, state) ends the chain — the terminating call still
|
|
2012
2034
|
returns state, so the final step participates in state aggregation. State is never fed to
|
|
2013
|
-
the model;
|
|
2014
|
-
|
|
2015
|
-
|
|
2035
|
+
the model; any picklable nest of builtin/numpy values works (dicts, lists, tuples, sets,
|
|
2036
|
+
numbers, strings, arrays — not user-defined classes, lambdas or open resources, since the
|
|
2037
|
+
platform deserializes state without the integration code), and it rides the platform queue
|
|
2038
|
+
on every step — accumulate reductions, not raw per-step tensors. The hook must be stateless and deterministically seeded (seed stochasticity from
|
|
2016
2039
|
sample_id) — the engine replays steps after crash recovery and relies on identical results.
|
|
2017
2040
|
|
|
2018
2041
|
latent_space_aggregation ('last_step' | 'mean') declares how the chain's latent-space
|
|
@@ -2844,6 +2867,13 @@ def tensorleap_element_instance_preprocess(
|
|
|
2844
2867
|
result = user_function()
|
|
2845
2868
|
found_instance_metadata = False
|
|
2846
2869
|
for preprocess_response in result:
|
|
2870
|
+
if preprocess_response.is_grouped:
|
|
2871
|
+
raise Exception(
|
|
2872
|
+
"tensorleap_element_instance_preprocess validation failed: a grouped "
|
|
2873
|
+
"(nested sample_ids) PreprocessResponse cannot also declare element "
|
|
2874
|
+
"instances -- instance ids are derived per scalar sample id, and the "
|
|
2875
|
+
"engine's grouped/batch fetch path has no per-instance id to mask by. "
|
|
2876
|
+
"Remove the instance encoders or don't group this dataset.")
|
|
2847
2877
|
sample_ids_to_instance_mappings = {}
|
|
2848
2878
|
instance_to_sample_ids_mappings = {}
|
|
2849
2879
|
all_sample_ids = preprocess_response.sample_ids.copy()
|
|
@@ -3114,10 +3144,12 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
3114
3144
|
if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
|
|
3115
3145
|
if grouped:
|
|
3116
3146
|
# A grouped result is a list of per-sample arrays (never stacked, so a
|
|
3117
|
-
# B=1-only model can be fed one sample at a time).
|
|
3118
|
-
#
|
|
3119
|
-
|
|
3120
|
-
|
|
3147
|
+
# B=1-only model can be fed one sample at a time). A user may also return a
|
|
3148
|
+
# supported (B, *) array; split it back into per-sample rows first (mirrors
|
|
3149
|
+
# LeapLoader._to_grouped_list) so both forms match the runtime path. Then add
|
|
3150
|
+
# a batch dim to each sample, mirroring the flat path's single-sample expand_dims.
|
|
3151
|
+
rows = result if isinstance(result, list) else [row for row in np.asarray(result)]
|
|
3152
|
+
result = [np.expand_dims(r, axis=0) for r in rows]
|
|
3121
3153
|
else:
|
|
3122
3154
|
batch_warning(result, user_function.__name__)
|
|
3123
3155
|
result = np.expand_dims(result, axis=0)
|
|
@@ -3216,7 +3248,7 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3216
3248
|
set_current("tensorleap_gt_encoder")
|
|
3217
3249
|
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
3218
3250
|
func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
|
|
3219
|
-
sample_id, preprocess_response = args
|
|
3251
|
+
sample_id, preprocess_response = args if len(args) != 0 else kwargs.values()
|
|
3220
3252
|
_validate_input_args(sample_id, preprocess_response)
|
|
3221
3253
|
|
|
3222
3254
|
grouped = isinstance(sample_id, list)
|
|
@@ -3228,10 +3260,12 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3228
3260
|
if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
|
|
3229
3261
|
if grouped:
|
|
3230
3262
|
# A grouped result is a list of per-sample arrays (never stacked, so a
|
|
3231
|
-
# B=1-only model can be fed one sample at a time).
|
|
3232
|
-
#
|
|
3233
|
-
|
|
3234
|
-
|
|
3263
|
+
# B=1-only model can be fed one sample at a time). A user may also return a
|
|
3264
|
+
# supported (B, *) array; split it back into per-sample rows first (mirrors
|
|
3265
|
+
# LeapLoader._to_grouped_list) so both forms match the runtime path. Then add
|
|
3266
|
+
# a batch dim to each sample, mirroring the flat path's single-sample expand_dims.
|
|
3267
|
+
rows = result if isinstance(result, list) else [row for row in np.asarray(result)]
|
|
3268
|
+
result = [np.expand_dims(r, axis=0) for r in rows]
|
|
3235
3269
|
else:
|
|
3236
3270
|
batch_warning(result, user_function.__name__)
|
|
3237
3271
|
result = np.expand_dims(result, axis=0)
|
|
@@ -41,6 +41,11 @@ def _serialize_sim_bounds(bounds) -> dict:
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
class LeapLoader(LeapLoaderBase):
|
|
44
|
+
# Opt-in ceiling (env var) on samples materialized per get_samples call — the engine sets it to
|
|
45
|
+
# turn a mistaken whole-group ask into a clear error instead of a silent worker OOM. Unset => no
|
|
46
|
+
# ceiling. See get_samples and grouped-fetch-oom-bug.md.
|
|
47
|
+
_grouped_fetch_max_samples_env = "LEAP_GROUPED_FETCH_MAX_SAMPLES_PER_CALL"
|
|
48
|
+
|
|
44
49
|
def __init__(self, code_path: str, code_entry_name: str):
|
|
45
50
|
super().__init__(code_path, code_entry_name)
|
|
46
51
|
|
|
@@ -77,8 +82,6 @@ class LeapLoader(LeapLoaderBase):
|
|
|
77
82
|
preprocess_result = global_leap_binder.get_preprocess_result()
|
|
78
83
|
self._preprocess_result_cached = preprocess_result
|
|
79
84
|
is_grouped = any(r.is_grouped for r in preprocess_result.values())
|
|
80
|
-
except Exception:
|
|
81
|
-
is_grouped = False
|
|
82
85
|
finally:
|
|
83
86
|
os.environ[mapping_runtime_mode_env_var_mame] = 'TRUE'
|
|
84
87
|
|
|
@@ -285,8 +288,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
285
288
|
latent_fn = global_leap_binder.setup_container.custom_latent_space.function
|
|
286
289
|
preprocess_state = preprocess_result[state]
|
|
287
290
|
if preprocess_state.is_grouped:
|
|
288
|
-
|
|
289
|
-
|
|
291
|
+
# Single-sample fetch: encode this sample only, not the whole group (same
|
|
292
|
+
# memory rationale as _get_dataset_handlers; see grouped-fetch-oom-bug.md).
|
|
293
|
+
self._locate_group(preprocess_state, sample_id) # validates group membership
|
|
294
|
+
custom_latent_space = self._to_grouped_list(latent_fn([sample_id], preprocess_state))[0]
|
|
290
295
|
else:
|
|
291
296
|
custom_latent_space = latent_fn(sample_id, preprocess_state)
|
|
292
297
|
instance_mask = self._get_instances_masks(state, sample_id, instance_id)
|
|
@@ -416,7 +421,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
416
421
|
test_result = DatasetTestResultPayload('preprocess')
|
|
417
422
|
try:
|
|
418
423
|
preprocess_result = self._preprocess_result()
|
|
419
|
-
if self.get_sample_id_type()
|
|
424
|
+
if issubclass(self.get_sample_id_type(), str):
|
|
420
425
|
max_allowed_item_size = np.dtype('<U256').itemsize
|
|
421
426
|
for state, preprocess_response in preprocess_result.items():
|
|
422
427
|
sample_ids_array = np.array(preprocess_response.flat_sample_ids)
|
|
@@ -480,6 +485,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
480
485
|
preprocess_response.tl_generated = True
|
|
481
486
|
if preprocess_response.length < 1:
|
|
482
487
|
raise ValueError("Simulation returned PreprocessResponse with length < 1")
|
|
488
|
+
if preprocess_response.is_grouped:
|
|
489
|
+
raise ValueError(
|
|
490
|
+
"Simulation returned a grouped PreprocessResponse; simulations must "
|
|
491
|
+
"return a flat (non-grouped) PreprocessResponse.")
|
|
483
492
|
preprocess_response.sample_ids = [0]
|
|
484
493
|
for handler in global_leap_binder.setup_container.inputs:
|
|
485
494
|
out1 = handler.function(preprocess_response.sample_ids[0], preprocess_response)
|
|
@@ -1001,11 +1010,17 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1001
1010
|
else:
|
|
1002
1011
|
preprocess_state = preprocess_result[state]
|
|
1003
1012
|
if preprocess_state.is_grouped:
|
|
1004
|
-
# Grouped dataset:
|
|
1005
|
-
|
|
1013
|
+
# Grouped dataset, SINGLE-sample fetch: encode only THIS sample, never the whole group.
|
|
1014
|
+
# Grouping is an I/O read-unit, not an encode-unit — the integration's per-file cache
|
|
1015
|
+
# makes the group's file decode once across consecutive per-sample calls, so a singleton
|
|
1016
|
+
# ([sample_id]) is both a valid single-group subset of the get_samples contract and
|
|
1017
|
+
# correct (samples in a group are independent; see _to_grouped_list). Handing the encoder
|
|
1018
|
+
# the whole group here just to index one sample out materialized B encoded tensors per
|
|
1019
|
+
# sample — a driver of the grouped-fetch worker OOM (see grouped-fetch-oom-bug.md).
|
|
1020
|
+
self._locate_group(preprocess_state, sample_id) # validates the id belongs to a group
|
|
1006
1021
|
for handler in handlers:
|
|
1007
|
-
|
|
1008
|
-
|
|
1022
|
+
result_agg[handler.name] = self._to_grouped_list(
|
|
1023
|
+
handler.function([sample_id], preprocess_state))[0]
|
|
1009
1024
|
return result_agg
|
|
1010
1025
|
for handler in handlers:
|
|
1011
1026
|
handler_result = handler.function(sample_id, preprocess_state)
|
|
@@ -1038,6 +1053,23 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1038
1053
|
f"get_samples received sample ids spanning {len(distinct_groups)} groups; a request "
|
|
1039
1054
|
f"must be confined to a single group. The engine partitions cross-group requests and "
|
|
1040
1055
|
f"issues one get_samples call per group.")
|
|
1056
|
+
# Opt-in safety net for the grouped-fetch OOM (grouped-fetch-oom-bug.md). This call
|
|
1057
|
+
# materializes len(group_ids) encoded samples at once; on a large group/file that peak is
|
|
1058
|
+
# what kernel-OOM-kills the worker. A group's file is decoded once by the integration's
|
|
1059
|
+
# per-file cache regardless of how the encode is chunked, so the engine should request
|
|
1060
|
+
# memory-bounded sub-chunks of a group (each a valid single-group subset, e.g.
|
|
1061
|
+
# get_samples(state, group_ids[i:j])) rather than the whole group. When the engine sets the
|
|
1062
|
+
# ceiling below, an over-large ask fails here with a message that names the lever instead of
|
|
1063
|
+
# a silent GENERIC_MEMORY_LIMIT kill. Unset => no ceiling (preserves current whole-group callers).
|
|
1064
|
+
max_per_call = os.environ.get(self._grouped_fetch_max_samples_env)
|
|
1065
|
+
if max_per_call is not None and len(group_ids) > int(max_per_call):
|
|
1066
|
+
raise ValueError(
|
|
1067
|
+
f"get_samples asked to materialize {len(group_ids)} samples in a single call, above "
|
|
1068
|
+
f"the configured ceiling {max_per_call} ({self._grouped_fetch_max_samples_env}). The "
|
|
1069
|
+
f"group's file is decoded once via the integration's per-file cache regardless of "
|
|
1070
|
+
f"chunking, so request memory-bounded sub-chunks of the group "
|
|
1071
|
+
f"(get_samples(state, group_ids[i:j])) instead of the whole group. "
|
|
1072
|
+
f"See grouped-fetch-oom-bug.md.")
|
|
1041
1073
|
inputs = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
1042
1074
|
for handler in global_leap_binder.setup_container.inputs}
|
|
1043
1075
|
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
@@ -1059,8 +1091,9 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1059
1091
|
f'{type(step_zero_result)} for sample {sid}.')
|
|
1060
1092
|
self._validate_autoregressive_step_inputs(step_zero_result[0], sid)
|
|
1061
1093
|
step_zero_per_sample.append(step_zero_result[0])
|
|
1062
|
-
|
|
1063
|
-
|
|
1094
|
+
if step_zero_per_sample:
|
|
1095
|
+
for key in self._grouped_dict_keys(step_zero_per_sample, autoregressive_handler.name):
|
|
1096
|
+
inputs[key] = [row[key] for row in step_zero_per_sample]
|
|
1064
1097
|
gt = None
|
|
1065
1098
|
if state != DataStateEnum.unlabeled:
|
|
1066
1099
|
gt = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
@@ -1069,8 +1102,8 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1069
1102
|
metadata: Dict[str, Any] = {}
|
|
1070
1103
|
metadata_is_none: Dict[str, Any] = {}
|
|
1071
1104
|
for handler in global_leap_binder.setup_container.metadata:
|
|
1072
|
-
rows = handler.function(group_ids, preprocess_state) #
|
|
1073
|
-
if rows and isinstance(rows[0], dict):
|
|
1105
|
+
rows = handler.function(group_ids, preprocess_state) # length-B sequence of scalars/dicts
|
|
1106
|
+
if len(rows) > 0 and isinstance(rows[0], dict):
|
|
1074
1107
|
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1075
1108
|
name = "{}_{}".format(handler.name, key)
|
|
1076
1109
|
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
@@ -1197,16 +1230,23 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1197
1230
|
sample_id = original_local_id
|
|
1198
1231
|
else:
|
|
1199
1232
|
preprocess_state = preprocess_result[state]
|
|
1200
|
-
group_ids, pos = (None, None)
|
|
1201
1233
|
if preprocess_state.is_grouped:
|
|
1202
|
-
|
|
1234
|
+
self._locate_group(preprocess_state, sample_id) # validates the id belongs to a group
|
|
1203
1235
|
for handler in global_leap_binder.setup_container.metadata:
|
|
1204
1236
|
if requested_metadata_names:
|
|
1205
1237
|
if not is_metadata_name_starts_with_handler_name(handler):
|
|
1206
1238
|
continue
|
|
1207
1239
|
|
|
1208
1240
|
if preprocess_state.is_grouped:
|
|
1209
|
-
|
|
1241
|
+
# Single-sample metadata fetch: encode only THIS sample, never the whole group —
|
|
1242
|
+
# same memory rationale as _get_dataset_handlers / the get_sample latent branch
|
|
1243
|
+
# (grouped-fetch-oom-bug.md). Metadata values are not guaranteed small scalars: a
|
|
1244
|
+
# metadata encoder may return an np.asarray or read the sample's heavy data to
|
|
1245
|
+
# compute a statistic, so materializing all B rows just to index one out is the same
|
|
1246
|
+
# OOM path this fix closes. The batched get_metadata_multiple_samples correctly keeps
|
|
1247
|
+
# the whole-group encode (amortized across many ids via its group_cache); a single
|
|
1248
|
+
# id does not need it. A singleton is a valid single-group subset of the contract.
|
|
1249
|
+
handler_result = handler.function([sample_id], preprocess_state)[0]
|
|
1210
1250
|
else:
|
|
1211
1251
|
handler_result = handler.function(sample_id, preprocess_state)
|
|
1212
1252
|
if isinstance(handler_result, dict):
|
|
@@ -1245,7 +1285,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1245
1285
|
if requested_metadata_names and not is_wanted(handler):
|
|
1246
1286
|
continue
|
|
1247
1287
|
rows = handler.function(group_ids, preprocess_state)
|
|
1248
|
-
if rows and isinstance(rows[0], dict):
|
|
1288
|
+
if len(rows) > 0 and isinstance(rows[0], dict):
|
|
1249
1289
|
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1250
1290
|
name = f'{handler.name}_{key}'
|
|
1251
1291
|
if requested_metadata_names and name not in requested_metadata_names:
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import io
|
|
1
2
|
import math
|
|
3
|
+
import pickle
|
|
2
4
|
import sys
|
|
3
5
|
from pathlib import Path
|
|
4
6
|
from types import TracebackType
|
|
@@ -15,11 +17,16 @@ from code_loader.contract.enums import DatasetMetadataType
|
|
|
15
17
|
def to_numpy_return_wrapper(encoder_function: SectionCallableInterface) -> SectionCallableInterface:
|
|
16
18
|
def numpy_encoder_function(idx: Union[int, str], samples: PreprocessResponse) -> npt.NDArray[np.float32]:
|
|
17
19
|
result = encoder_function(idx, samples)
|
|
18
|
-
if isinstance(
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
|
|
20
|
+
if isinstance(idx, list):
|
|
21
|
+
# Grouped call (idx is a list of sample ids): never stack the per-sample results into a
|
|
22
|
+
# (B, *) batch — grouping is only a storage read-unit; batching the group to the model's
|
|
23
|
+
# B is the engine's single responsibility. Gate on the call shape (idx is a list), not
|
|
24
|
+
# the result shape, so a flat encoder returning a list of arrays still stacks below.
|
|
25
|
+
# Normalize each sample independently (a user-stacked (B, *) array is split back into its
|
|
26
|
+
# rows) so a ragged group — variable per-sample shapes, e.g. token lists of differing
|
|
27
|
+
# length — is preserved instead of crashing np.array() on an inhomogeneous shape.
|
|
28
|
+
rows = result if isinstance(result, list) else np.asarray(result)
|
|
29
|
+
return [np.asarray(sample) for sample in rows]
|
|
23
30
|
numpy_result: npt.NDArray[np.float32] = np.array(result)
|
|
24
31
|
return numpy_result
|
|
25
32
|
|
|
@@ -109,27 +116,31 @@ def get_metadata_type_from_variable(variable: Union[Optional[str], int, bool, Op
|
|
|
109
116
|
|
|
110
117
|
|
|
111
118
|
|
|
112
|
-
|
|
119
|
+
# The platform queue transports state as pickled payloads which the engine deserializes
|
|
120
|
+
# WITHOUT the integration code loaded, so a class defined in the user's modules round-trips
|
|
121
|
+
# locally but crashes on the engine. Only modules importable on the engine may resolve.
|
|
122
|
+
_STATE_SAFE_PICKLE_MODULES = ('builtins', 'copyreg', 'collections', 'numpy', 'datetime')
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class _EngineSafeUnpickler(pickle.Unpickler):
|
|
126
|
+
def find_class(self, module: str, name: str) -> Any:
|
|
127
|
+
if module.split('.')[0] in _STATE_SAFE_PICKLE_MODULES:
|
|
128
|
+
return super().find_class(module, name)
|
|
129
|
+
raise pickle.UnpicklingError(
|
|
130
|
+
f'{module}.{name} is defined in integration code, which is not importable on the '
|
|
131
|
+
f'platform engine')
|
|
113
132
|
|
|
114
133
|
|
|
115
134
|
def validate_autoregressive_state_types(value: Any, path: str = 'state') -> None:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
raise AssertionError(
|
|
120
|
-
f'autoregressive state validation failed: dict key {key!r} at {path} must be '
|
|
121
|
-
f'a string. Got {type(key).__name__}.')
|
|
122
|
-
validate_autoregressive_state_types(item, f'{path}[{key!r}]')
|
|
123
|
-
return
|
|
124
|
-
if isinstance(value, (list, tuple)):
|
|
125
|
-
for i, item in enumerate(value):
|
|
126
|
-
validate_autoregressive_state_types(item, f'{path}[{i}]')
|
|
127
|
-
return
|
|
128
|
-
if not isinstance(value, AUTOREGRESSIVE_STATE_LEAF_TYPES):
|
|
135
|
+
try:
|
|
136
|
+
_EngineSafeUnpickler(io.BytesIO(pickle.dumps(value))).load()
|
|
137
|
+
except Exception as e:
|
|
129
138
|
raise AssertionError(
|
|
130
|
-
f'autoregressive state validation failed: {path}
|
|
131
|
-
|
|
132
|
-
|
|
139
|
+
f'autoregressive state validation failed: {path} is not serializable ({e}). State '
|
|
140
|
+
'is serialized to the platform queue on every chain step and deserialized without '
|
|
141
|
+
'the integration code — any picklable nest of builtin/numpy values works (dicts, '
|
|
142
|
+
'lists, tuples, sets, numbers, strings, arrays), but user-defined classes, lambdas '
|
|
143
|
+
'and open resources do not.') from e
|
|
133
144
|
|
|
134
145
|
|
|
135
146
|
def autoregressive_nests_equal(a: Any, b: Any) -> bool:
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
|
|
6
|
+
from code_loader.contract.visualizer_classes import LeapImage, LeapGraph, LeapHorizontalBar, LeapText, \
|
|
7
|
+
LeapImageMask, LeapTextMask, LeapVideo, LeapAudio
|
|
8
|
+
from code_loader.utils import rescale_min_max
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DefaultVisualizer(Enum):
|
|
12
|
+
Image = 'Image'
|
|
13
|
+
Video = 'Video'
|
|
14
|
+
Audio = 'Audio'
|
|
15
|
+
Graph = 'Graph'
|
|
16
|
+
HorizontalBar = 'HorizontalBar'
|
|
17
|
+
Text = 'Text'
|
|
18
|
+
ImageMask = 'ImageMask'
|
|
19
|
+
TextMask = 'TextMask'
|
|
20
|
+
RawData = 'RawData'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def default_image_visualizer(data: npt.NDArray[np.float32]) -> LeapImage:
|
|
24
|
+
rescaled_data = rescale_min_max(data[0])
|
|
25
|
+
return LeapImage(rescaled_data)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def default_video_visualizer(data: npt.NDArray[np.float32]) -> LeapVideo:
|
|
29
|
+
return LeapVideo(data[0])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_MAX_WAVEFORM_POINTS = 2000
|
|
33
|
+
_VISUAL_KINDS = ('waveform', 'spectrogram', 'image')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _to_mono(audio: npt.NDArray) -> npt.NDArray:
|
|
37
|
+
a = np.squeeze(np.asarray(audio))
|
|
38
|
+
if a.ndim == 2: # mix down: channels are the shorter axis
|
|
39
|
+
a = a.mean(axis=int(np.argmin(a.shape)))
|
|
40
|
+
if a.ndim != 1:
|
|
41
|
+
raise ValueError(f'audio must be reducible to a mono 1-D signal, got shape {np.asarray(audio).shape}')
|
|
42
|
+
return a.astype(np.float32)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _waveform_graph(series: npt.NDArray, duration: float) -> LeapGraph:
|
|
46
|
+
s = np.squeeze(np.asarray(series)).astype(np.float32)
|
|
47
|
+
# ponytail: recharts chokes on 100k+ points; stride down for the default view. x_range keeps full time span.
|
|
48
|
+
step = max(1, s.shape[0] // _MAX_WAVEFORM_POINTS)
|
|
49
|
+
s = s[::step].reshape(-1, 1)
|
|
50
|
+
return LeapGraph(s, x_label='Time [s]', y_label='Amplitude', x_range=(0.0, duration))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def default_audio_visualizer(audio: npt.NDArray,
|
|
54
|
+
sample_rate: int,
|
|
55
|
+
visual: npt.NDArray,
|
|
56
|
+
visual_kind: str,
|
|
57
|
+
time_axis_index: int = -1) -> LeapAudio:
|
|
58
|
+
"""Package a playable audio sample plus the visual a playhead runs through.
|
|
59
|
+
|
|
60
|
+
audio: waveform samples (mono [N], or multi-channel that is mixed down).
|
|
61
|
+
sample_rate: samples per second.
|
|
62
|
+
visual: the array to display. For 'waveform' a 1-D series; for 'spectrogram'
|
|
63
|
+
a raw 2-D [freq, time] (or [time, freq]) array to be rendered grayscale;
|
|
64
|
+
for 'image' an already-rendered [H, W, 1] or [H, W, 3] image (e.g. a
|
|
65
|
+
spectrogram the caller already ran through their own colormap).
|
|
66
|
+
visual_kind: 'waveform', 'spectrogram', or 'image'. The caller states this
|
|
67
|
+
explicitly; the shape of `visual` is never inspected to guess it.
|
|
68
|
+
time_axis_index: for 'spectrogram', which axis of `visual` is time (moved to columns).
|
|
69
|
+
Unused for 'waveform'/'image'.
|
|
70
|
+
"""
|
|
71
|
+
if visual_kind not in _VISUAL_KINDS:
|
|
72
|
+
raise ValueError(f"visual_kind must be one of {_VISUAL_KINDS}, got {visual_kind!r}")
|
|
73
|
+
|
|
74
|
+
mono = _to_mono(audio)
|
|
75
|
+
duration = mono.shape[0] / float(sample_rate)
|
|
76
|
+
x_range = (0.0, duration)
|
|
77
|
+
v = np.asarray(visual)
|
|
78
|
+
|
|
79
|
+
if visual_kind == 'waveform':
|
|
80
|
+
return LeapAudio(mono, sample_rate, _waveform_graph(v, duration), x_range)
|
|
81
|
+
|
|
82
|
+
if visual_kind == 'image':
|
|
83
|
+
return LeapAudio(mono, sample_rate, LeapImage(v), x_range)
|
|
84
|
+
|
|
85
|
+
# spectrogram
|
|
86
|
+
spec = np.squeeze(v)
|
|
87
|
+
if spec.ndim != 2:
|
|
88
|
+
raise ValueError(f'spectrogram visual must be 2-D (freq x time), got shape {v.shape}')
|
|
89
|
+
if time_axis_index % spec.ndim == 0: # move time onto the horizontal axis (columns)
|
|
90
|
+
spec = spec.T
|
|
91
|
+
img = rescale_min_max(spec.astype(np.float32))[..., None] # [freq, time, 1]
|
|
92
|
+
return LeapAudio(mono, sample_rate, LeapImage(img), x_range)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def default_graph_visualizer(data: npt.NDArray[np.float32]) -> LeapGraph:
|
|
96
|
+
return LeapGraph(data[0])
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def default_horizontal_bar_visualizer(data: npt.NDArray[np.float32]) -> LeapHorizontalBar:
|
|
100
|
+
labels = [str(index) for index in range(data.shape[-1])]
|
|
101
|
+
return LeapHorizontalBar(data[0], labels)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def default_word_visualizer(data: npt.NDArray[np.float32]) -> LeapText:
|
|
105
|
+
if len(data.shape) == 2 and data.shape[0] == 1:
|
|
106
|
+
data = data[0]
|
|
107
|
+
if hasattr(data, 'tolist'):
|
|
108
|
+
data = data.tolist()
|
|
109
|
+
words = [str(index[0]) if type(index) is list else str(index) for index in data]
|
|
110
|
+
return LeapText(words)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def default_raw_data_visualizer(data: npt.NDArray[np.float32]) -> LeapText:
|
|
114
|
+
return LeapText([str(data)])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def default_image_mask_visualizer(mask: npt.NDArray[np.float32], image: npt.NDArray[np.float32]) -> LeapImageMask:
|
|
118
|
+
if image.shape[0] == 1:
|
|
119
|
+
image = image[0]
|
|
120
|
+
if mask.shape[0] == 1:
|
|
121
|
+
mask = mask[0]
|
|
122
|
+
|
|
123
|
+
n_different_labels = mask.shape[-1]
|
|
124
|
+
labels = [str(i) for i in range(n_different_labels)]
|
|
125
|
+
|
|
126
|
+
if len(mask.shape) > 2:
|
|
127
|
+
if mask.shape[-1] == 1:
|
|
128
|
+
mask = np.squeeze(mask, axis=-1)
|
|
129
|
+
else:
|
|
130
|
+
mask = np.argmax(mask, axis=-1)
|
|
131
|
+
|
|
132
|
+
return LeapImageMask(mask.astype(np.uint8), image.astype(np.float32), labels)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def default_text_mask_visualizer(mask: npt.NDArray[np.float32], text_data: npt.NDArray[np.float32]) -> LeapTextMask:
|
|
136
|
+
mask = mask[0]
|
|
137
|
+
text_data = text_data[0]
|
|
138
|
+
|
|
139
|
+
words = default_word_visualizer(text_data).data
|
|
140
|
+
n_different_labels = mask.shape[-1]
|
|
141
|
+
labels = [str(i) for i in range(n_different_labels)]
|
|
142
|
+
|
|
143
|
+
if len(mask.shape) > 1:
|
|
144
|
+
if mask.shape[-1] == 1:
|
|
145
|
+
mask = np.squeeze(mask, axis=-1)
|
|
146
|
+
else:
|
|
147
|
+
mask = np.argmax(mask, axis=-1)
|
|
148
|
+
|
|
149
|
+
return LeapTextMask(mask.astype(np.uint8), words, labels)
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
from enum import Enum
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import numpy.typing as npt
|
|
5
|
-
|
|
6
|
-
from code_loader.contract.visualizer_classes import LeapImage, LeapGraph, LeapHorizontalBar, LeapText, \
|
|
7
|
-
LeapImageMask, LeapTextMask, LeapVideo
|
|
8
|
-
from code_loader.utils import rescale_min_max
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class DefaultVisualizer(Enum):
|
|
12
|
-
Image = 'Image'
|
|
13
|
-
Video = 'Video'
|
|
14
|
-
Graph = 'Graph'
|
|
15
|
-
HorizontalBar = 'HorizontalBar'
|
|
16
|
-
Text = 'Text'
|
|
17
|
-
ImageMask = 'ImageMask'
|
|
18
|
-
TextMask = 'TextMask'
|
|
19
|
-
RawData = 'RawData'
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def default_image_visualizer(data: npt.NDArray[np.float32]) -> LeapImage:
|
|
23
|
-
rescaled_data = rescale_min_max(data[0])
|
|
24
|
-
return LeapImage(rescaled_data)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def default_video_visualizer(data: npt.NDArray[np.float32]) -> LeapVideo:
|
|
28
|
-
return LeapVideo(data[0])
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def default_graph_visualizer(data: npt.NDArray[np.float32]) -> LeapGraph:
|
|
32
|
-
return LeapGraph(data[0])
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def default_horizontal_bar_visualizer(data: npt.NDArray[np.float32]) -> LeapHorizontalBar:
|
|
36
|
-
labels = [str(index) for index in range(data.shape[-1])]
|
|
37
|
-
return LeapHorizontalBar(data[0], labels)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def default_word_visualizer(data: npt.NDArray[np.float32]) -> LeapText:
|
|
41
|
-
if len(data.shape) == 2 and data.shape[0] == 1:
|
|
42
|
-
data = data[0]
|
|
43
|
-
if hasattr(data, 'tolist'):
|
|
44
|
-
data = data.tolist()
|
|
45
|
-
words = [str(index[0]) if type(index) is list else str(index) for index in data]
|
|
46
|
-
return LeapText(words)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def default_raw_data_visualizer(data: npt.NDArray[np.float32]) -> LeapText:
|
|
50
|
-
return LeapText([str(data)])
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def default_image_mask_visualizer(mask: npt.NDArray[np.float32], image: npt.NDArray[np.float32]) -> LeapImageMask:
|
|
54
|
-
if image.shape[0] == 1:
|
|
55
|
-
image = image[0]
|
|
56
|
-
if mask.shape[0] == 1:
|
|
57
|
-
mask = mask[0]
|
|
58
|
-
|
|
59
|
-
n_different_labels = mask.shape[-1]
|
|
60
|
-
labels = [str(i) for i in range(n_different_labels)]
|
|
61
|
-
|
|
62
|
-
if len(mask.shape) > 2:
|
|
63
|
-
if mask.shape[-1] == 1:
|
|
64
|
-
mask = np.squeeze(mask, axis=-1)
|
|
65
|
-
else:
|
|
66
|
-
mask = np.argmax(mask, axis=-1)
|
|
67
|
-
|
|
68
|
-
return LeapImageMask(mask.astype(np.uint8), image.astype(np.float32), labels)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def default_text_mask_visualizer(mask: npt.NDArray[np.float32], text_data: npt.NDArray[np.float32]) -> LeapTextMask:
|
|
72
|
-
mask = mask[0]
|
|
73
|
-
text_data = text_data[0]
|
|
74
|
-
|
|
75
|
-
words = default_word_visualizer(text_data).data
|
|
76
|
-
n_different_labels = mask.shape[-1]
|
|
77
|
-
labels = [str(i) for i in range(n_different_labels)]
|
|
78
|
-
|
|
79
|
-
if len(mask.shape) > 1:
|
|
80
|
-
if mask.shape[-1] == 1:
|
|
81
|
-
mask = np.squeeze(mask, axis=-1)
|
|
82
|
-
else:
|
|
83
|
-
mask = np.argmax(mask, axis=-1)
|
|
84
|
-
|
|
85
|
-
return LeapTextMask(mask.astype(np.uint8), words, labels)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/contract/responsedataclasses.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/cli_config_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/experiment_api/experiment_context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_loader-1.0.196.dev0 → code_loader-1.0.197}/code_loader/plot_functions/plot_functions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|