code-loader 1.0.196.dev1__py3-none-any.whl → 1.0.197__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.
- code_loader/contract/datasetclasses.py +9 -1
- code_loader/contract/enums.py +1 -0
- code_loader/contract/visualizer_classes.py +43 -1
- code_loader/inner_leap_binder/leapbinder.py +4 -0
- code_loader/inner_leap_binder/leapbinder_decorators.py +32 -3
- code_loader/leaploader.py +57 -15
- code_loader/utils.py +10 -7
- code_loader/visualizers/default_visualizers.py +66 -2
- {code_loader-1.0.196.dev1.dist-info → code_loader-1.0.197.dist-info}/METADATA +1 -1
- {code_loader-1.0.196.dev1.dist-info → code_loader-1.0.197.dist-info}/RECORD +12 -12
- {code_loader-1.0.196.dev1.dist-info → code_loader-1.0.197.dist-info}/LICENSE +0 -0
- {code_loader-1.0.196.dev1.dist-info → code_loader-1.0.197.dist-info}/WHEEL +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
|
|
code_loader/contract/enums.py
CHANGED
|
@@ -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
|
}
|
|
@@ -892,6 +892,10 @@ class LeapBinder:
|
|
|
892
892
|
preprocess_response.tl_generated = True
|
|
893
893
|
if not preprocess_response.length or preprocess_response.length < 1:
|
|
894
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))
|
|
895
899
|
preprocess_response.sample_ids = cast(List[Union[int, str]], [0])
|
|
896
900
|
sim_sample_ids = cast(List[Union[int, str]], preprocess_response.sample_ids)
|
|
897
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])
|
|
@@ -2845,6 +2867,13 @@ def tensorleap_element_instance_preprocess(
|
|
|
2845
2867
|
result = user_function()
|
|
2846
2868
|
found_instance_metadata = False
|
|
2847
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.")
|
|
2848
2877
|
sample_ids_to_instance_mappings = {}
|
|
2849
2878
|
instance_to_sample_ids_mappings = {}
|
|
2850
2879
|
all_sample_ids = preprocess_response.sample_ids.copy()
|
|
@@ -3219,7 +3248,7 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3219
3248
|
set_current("tensorleap_gt_encoder")
|
|
3220
3249
|
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
3221
3250
|
func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
|
|
3222
|
-
sample_id, preprocess_response = args
|
|
3251
|
+
sample_id, preprocess_response = args if len(args) != 0 else kwargs.values()
|
|
3223
3252
|
_validate_input_args(sample_id, preprocess_response)
|
|
3224
3253
|
|
|
3225
3254
|
grouped = isinstance(sample_id, list)
|
code_loader/leaploader.py
CHANGED
|
@@ -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
|
|
|
@@ -283,8 +288,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
283
288
|
latent_fn = global_leap_binder.setup_container.custom_latent_space.function
|
|
284
289
|
preprocess_state = preprocess_result[state]
|
|
285
290
|
if preprocess_state.is_grouped:
|
|
286
|
-
|
|
287
|
-
|
|
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]
|
|
288
295
|
else:
|
|
289
296
|
custom_latent_space = latent_fn(sample_id, preprocess_state)
|
|
290
297
|
instance_mask = self._get_instances_masks(state, sample_id, instance_id)
|
|
@@ -414,7 +421,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
414
421
|
test_result = DatasetTestResultPayload('preprocess')
|
|
415
422
|
try:
|
|
416
423
|
preprocess_result = self._preprocess_result()
|
|
417
|
-
if self.get_sample_id_type()
|
|
424
|
+
if issubclass(self.get_sample_id_type(), str):
|
|
418
425
|
max_allowed_item_size = np.dtype('<U256').itemsize
|
|
419
426
|
for state, preprocess_response in preprocess_result.items():
|
|
420
427
|
sample_ids_array = np.array(preprocess_response.flat_sample_ids)
|
|
@@ -478,6 +485,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
478
485
|
preprocess_response.tl_generated = True
|
|
479
486
|
if preprocess_response.length < 1:
|
|
480
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.")
|
|
481
492
|
preprocess_response.sample_ids = [0]
|
|
482
493
|
for handler in global_leap_binder.setup_container.inputs:
|
|
483
494
|
out1 = handler.function(preprocess_response.sample_ids[0], preprocess_response)
|
|
@@ -999,11 +1010,17 @@ class LeapLoader(LeapLoaderBase):
|
|
|
999
1010
|
else:
|
|
1000
1011
|
preprocess_state = preprocess_result[state]
|
|
1001
1012
|
if preprocess_state.is_grouped:
|
|
1002
|
-
# Grouped dataset:
|
|
1003
|
-
|
|
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
|
|
1004
1021
|
for handler in handlers:
|
|
1005
|
-
|
|
1006
|
-
|
|
1022
|
+
result_agg[handler.name] = self._to_grouped_list(
|
|
1023
|
+
handler.function([sample_id], preprocess_state))[0]
|
|
1007
1024
|
return result_agg
|
|
1008
1025
|
for handler in handlers:
|
|
1009
1026
|
handler_result = handler.function(sample_id, preprocess_state)
|
|
@@ -1036,6 +1053,23 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1036
1053
|
f"get_samples received sample ids spanning {len(distinct_groups)} groups; a request "
|
|
1037
1054
|
f"must be confined to a single group. The engine partitions cross-group requests and "
|
|
1038
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.")
|
|
1039
1073
|
inputs = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
1040
1074
|
for handler in global_leap_binder.setup_container.inputs}
|
|
1041
1075
|
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
@@ -1057,8 +1091,9 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1057
1091
|
f'{type(step_zero_result)} for sample {sid}.')
|
|
1058
1092
|
self._validate_autoregressive_step_inputs(step_zero_result[0], sid)
|
|
1059
1093
|
step_zero_per_sample.append(step_zero_result[0])
|
|
1060
|
-
|
|
1061
|
-
|
|
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]
|
|
1062
1097
|
gt = None
|
|
1063
1098
|
if state != DataStateEnum.unlabeled:
|
|
1064
1099
|
gt = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
@@ -1067,8 +1102,8 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1067
1102
|
metadata: Dict[str, Any] = {}
|
|
1068
1103
|
metadata_is_none: Dict[str, Any] = {}
|
|
1069
1104
|
for handler in global_leap_binder.setup_container.metadata:
|
|
1070
|
-
rows = handler.function(group_ids, preprocess_state) #
|
|
1071
|
-
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):
|
|
1072
1107
|
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1073
1108
|
name = "{}_{}".format(handler.name, key)
|
|
1074
1109
|
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
@@ -1195,16 +1230,23 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1195
1230
|
sample_id = original_local_id
|
|
1196
1231
|
else:
|
|
1197
1232
|
preprocess_state = preprocess_result[state]
|
|
1198
|
-
group_ids, pos = (None, None)
|
|
1199
1233
|
if preprocess_state.is_grouped:
|
|
1200
|
-
|
|
1234
|
+
self._locate_group(preprocess_state, sample_id) # validates the id belongs to a group
|
|
1201
1235
|
for handler in global_leap_binder.setup_container.metadata:
|
|
1202
1236
|
if requested_metadata_names:
|
|
1203
1237
|
if not is_metadata_name_starts_with_handler_name(handler):
|
|
1204
1238
|
continue
|
|
1205
1239
|
|
|
1206
1240
|
if preprocess_state.is_grouped:
|
|
1207
|
-
|
|
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]
|
|
1208
1250
|
else:
|
|
1209
1251
|
handler_result = handler.function(sample_id, preprocess_state)
|
|
1210
1252
|
if isinstance(handler_result, dict):
|
|
@@ -1243,7 +1285,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1243
1285
|
if requested_metadata_names and not is_wanted(handler):
|
|
1244
1286
|
continue
|
|
1245
1287
|
rows = handler.function(group_ids, preprocess_state)
|
|
1246
|
-
if rows and isinstance(rows[0], dict):
|
|
1288
|
+
if len(rows) > 0 and isinstance(rows[0], dict):
|
|
1247
1289
|
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1248
1290
|
name = f'{handler.name}_{key}'
|
|
1249
1291
|
if requested_metadata_names and name not in requested_metadata_names:
|
code_loader/utils.py
CHANGED
|
@@ -17,13 +17,16 @@ from code_loader.contract.enums import DatasetMetadataType
|
|
|
17
17
|
def to_numpy_return_wrapper(encoder_function: SectionCallableInterface) -> SectionCallableInterface:
|
|
18
18
|
def numpy_encoder_function(idx: Union[int, str], samples: PreprocessResponse) -> npt.NDArray[np.float32]:
|
|
19
19
|
result = encoder_function(idx, samples)
|
|
20
|
-
if isinstance(idx, list)
|
|
21
|
-
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# the
|
|
25
|
-
#
|
|
26
|
-
|
|
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]
|
|
27
30
|
numpy_result: npt.NDArray[np.float32] = np.array(result)
|
|
28
31
|
return numpy_result
|
|
29
32
|
|
|
@@ -4,20 +4,21 @@ import numpy as np
|
|
|
4
4
|
import numpy.typing as npt
|
|
5
5
|
|
|
6
6
|
from code_loader.contract.visualizer_classes import LeapImage, LeapGraph, LeapHorizontalBar, LeapText, \
|
|
7
|
-
LeapImageMask, LeapTextMask, LeapVideo
|
|
7
|
+
LeapImageMask, LeapTextMask, LeapVideo, LeapAudio
|
|
8
8
|
from code_loader.utils import rescale_min_max
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class DefaultVisualizer(Enum):
|
|
12
12
|
Image = 'Image'
|
|
13
13
|
Video = 'Video'
|
|
14
|
+
Audio = 'Audio'
|
|
14
15
|
Graph = 'Graph'
|
|
15
16
|
HorizontalBar = 'HorizontalBar'
|
|
16
17
|
Text = 'Text'
|
|
17
18
|
ImageMask = 'ImageMask'
|
|
18
19
|
TextMask = 'TextMask'
|
|
19
20
|
RawData = 'RawData'
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
|
|
22
23
|
def default_image_visualizer(data: npt.NDArray[np.float32]) -> LeapImage:
|
|
23
24
|
rescaled_data = rescale_min_max(data[0])
|
|
@@ -28,6 +29,69 @@ def default_video_visualizer(data: npt.NDArray[np.float32]) -> LeapVideo:
|
|
|
28
29
|
return LeapVideo(data[0])
|
|
29
30
|
|
|
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
|
+
|
|
31
95
|
def default_graph_visualizer(data: npt.NDArray[np.float32]) -> LeapGraph:
|
|
32
96
|
return LeapGraph(data[0])
|
|
33
97
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
LICENSE,sha256=qIwWjdspQeSMTtnFZBC8MuT-95L02FPvzRUdWFxrwJY,1067
|
|
2
2
|
code_loader/__init__.py,sha256=outxRQ0M-zMfV0QGVJmAed5qWfRmyD0TV6-goEGAzBw,406
|
|
3
3
|
code_loader/contract/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
code_loader/contract/datasetclasses.py,sha256=
|
|
5
|
-
code_loader/contract/enums.py,sha256=
|
|
4
|
+
code_loader/contract/datasetclasses.py,sha256=qJoC2pPrVLwJiNGQlPUmnM4vgGNxEv4LHPjovVunTGc,16741
|
|
5
|
+
code_loader/contract/enums.py,sha256=__GkPkwAXi2agmDGtEQgbMucPm4-n80maG6n_MmObPA,1691
|
|
6
6
|
code_loader/contract/exceptions.py,sha256=jWqu5i7t-0IG0jGRsKF4DjJdrsdpJjIYpUkN1F4RiyQ,51
|
|
7
7
|
code_loader/contract/mapping.py,sha256=sWJhpng-IkOzQnWQdMT5w2ZZ3X1Z_OOzSwCLXIS7oxE,1446
|
|
8
8
|
code_loader/contract/responsedataclasses.py,sha256=5VFgGjRubMW8ItMPils3rkBNejunCGLaa192AIi-xko,4925
|
|
9
9
|
code_loader/contract/sim_config.py,sha256=le8KMALZiP0WU4UcuKnTOSWBW2rNjpnWYfII502NqDM,3493
|
|
10
|
-
code_loader/contract/visualizer_classes.py,sha256=
|
|
10
|
+
code_loader/contract/visualizer_classes.py,sha256=vhdufY1nDhEBbtqrLgRe0bZiNCtOmsKPFxbvfbTZVUs,18624
|
|
11
11
|
code_loader/default_losses.py,sha256=NoOQym1106bDN5dcIk56Elr7ZG5quUHArqfP5-Nyxyo,1139
|
|
12
12
|
code_loader/default_metrics.py,sha256=2XSlyNw_XLDGSJDoz5W_Evi5wbL0dhwq24pPr15vSPc,5025
|
|
13
13
|
code_loader/experiment_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -21,18 +21,18 @@ code_loader/experiment_api/types.py,sha256=MY8xFARHwdVA7p4dxyhD60ShmttgTvb4qdp1o
|
|
|
21
21
|
code_loader/experiment_api/utils.py,sha256=XZHtxge12TS4H4-8PjV3sKuhp8Ud6ojAiIzTZJEqBqc,3304
|
|
22
22
|
code_loader/experiment_api/workingspace_config_utils.py,sha256=DLzXQCg4dgTV_YgaSbeTVzq-2ja_SQw4zi7LXwKL9cY,990
|
|
23
23
|
code_loader/inner_leap_binder/__init__.py,sha256=koOlJyMNYzGbEsoIbXathSmQ-L38N_pEXH_HvL7beXU,99
|
|
24
|
-
code_loader/inner_leap_binder/leapbinder.py,sha256=
|
|
25
|
-
code_loader/inner_leap_binder/leapbinder_decorators.py,sha256=
|
|
26
|
-
code_loader/leaploader.py,sha256=
|
|
24
|
+
code_loader/inner_leap_binder/leapbinder.py,sha256=RSYiWtIxrIV7a466oPTEupP5SF9eOkZZotr_rp7yCHs,56880
|
|
25
|
+
code_loader/inner_leap_binder/leapbinder_decorators.py,sha256=g_fZw4OlRAJ2d_8bjRBnazNNpzi6pJY0TV3BYJgXP3s,185922
|
|
26
|
+
code_loader/leaploader.py,sha256=vJDQLC6rq12PiBiDAeKttgSXK72QFQMCYLvjp_Jf2Uo,87135
|
|
27
27
|
code_loader/leaploaderbase.py,sha256=PvBU_2OQqBfJLDMsbK1s372954eYr4Y3O4r18y5S7uY,10739
|
|
28
28
|
code_loader/mixpanel_tracker.py,sha256=rNwRmFifNbdUoqLQvvhhgpKczWpWiEmd8MfyJe27sxw,9131
|
|
29
29
|
code_loader/plot_functions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
30
|
code_loader/plot_functions/plot_functions.py,sha256=2DC-zlVaN13P4VNx5d8csgs80C6SisaeP1-Kq2LW7iM,16075
|
|
31
31
|
code_loader/plot_functions/visualize.py,sha256=gsBAYYkwMh7jIpJeDMPS8G4CW-pxwx6LznoQIvi4vpo,657
|
|
32
|
-
code_loader/utils.py,sha256=
|
|
32
|
+
code_loader/utils.py,sha256=Yj06BbS1UIjN9Cv2eQBjDfdDbpQlABAg6KwD3sB1BZ0,7496
|
|
33
33
|
code_loader/visualizers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
-
code_loader/visualizers/default_visualizers.py,sha256=
|
|
35
|
-
code_loader-1.0.
|
|
36
|
-
code_loader-1.0.
|
|
37
|
-
code_loader-1.0.
|
|
38
|
-
code_loader-1.0.
|
|
34
|
+
code_loader/visualizers/default_visualizers.py,sha256=c60vSpw48yVlIF2jIZ-5B6aDdCYBXKXfKzFpTDsAZAw,5467
|
|
35
|
+
code_loader-1.0.197.dist-info/LICENSE,sha256=qIwWjdspQeSMTtnFZBC8MuT-95L02FPvzRUdWFxrwJY,1067
|
|
36
|
+
code_loader-1.0.197.dist-info/METADATA,sha256=MDgLFFdlskU6Gf1ccQWZC9Xk9-epYyr-aMYB9jFaqz4,1090
|
|
37
|
+
code_loader-1.0.197.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
38
|
+
code_loader-1.0.197.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|