code-loader 1.0.195.dev0__py3-none-any.whl → 1.0.196__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 +66 -10
- code_loader/inner_leap_binder/leapbinder.py +75 -7
- code_loader/inner_leap_binder/leapbinder_decorators.py +211 -62
- code_loader/leaploader.py +251 -11
- code_loader/utils.py +10 -0
- {code_loader-1.0.195.dev0.dist-info → code_loader-1.0.196.dist-info}/METADATA +1 -1
- {code_loader-1.0.195.dev0.dist-info → code_loader-1.0.196.dist-info}/RECORD +9 -9
- {code_loader-1.0.195.dev0.dist-info → code_loader-1.0.196.dist-info}/LICENSE +0 -0
- {code_loader-1.0.195.dev0.dist-info → code_loader-1.0.196.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import warnings
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
|
-
from typing import Any, Callable, List, Optional, Dict, Union, Type, Literal, Tuple
|
|
3
|
+
from typing import Any, Callable, List, Optional, Dict, Union, Type, Literal, Tuple, cast
|
|
4
4
|
import re
|
|
5
5
|
import numpy as np
|
|
6
6
|
import numpy.typing as npt
|
|
@@ -15,6 +15,8 @@ custom_latent_space_attribute = "custom_latent_space"
|
|
|
15
15
|
|
|
16
16
|
_simulation_context: Dict[str, bool] = {"active": False}
|
|
17
17
|
|
|
18
|
+
SampleId = Union[int, str]
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
@dataclass
|
|
20
22
|
class PreprocessResponse:
|
|
@@ -39,27 +41,54 @@ class PreprocessResponse:
|
|
|
39
41
|
"""
|
|
40
42
|
length: Optional[int] = None # Deprecated. Please use sample_ids instead
|
|
41
43
|
data: Any = None
|
|
42
|
-
sample_ids: Optional[Union[List[
|
|
44
|
+
sample_ids: Optional[Union[List[SampleId], List[List[SampleId]]]] = None
|
|
43
45
|
state: Optional[DataStateType] = None
|
|
44
46
|
sample_id_type: Optional[Union[Type[str], Type[int]]] = None
|
|
45
47
|
sample_ids_to_instance_mappings: Optional[Dict[str, List[str]]] = None # in use only for element instance
|
|
46
48
|
instance_to_sample_ids_mappings: Optional[Dict[str, str]] = None # in use only for element instance
|
|
47
49
|
tl_generated: bool = False
|
|
50
|
+
_grouped: bool = field(default=False, init=False, repr=False, compare=False)
|
|
51
|
+
_group_pos_cache: Optional[Dict[SampleId, Tuple[List[SampleId], int]]] = field(
|
|
52
|
+
default=None, init=False, repr=False, compare=False)
|
|
48
53
|
|
|
49
54
|
def __post_init__(self) -> None:
|
|
50
55
|
assert self.sample_ids_to_instance_mappings is None, f"Keep sample_ids_to_instance_mappings None when initializing PreprocessResponse"
|
|
51
56
|
assert self.instance_to_sample_ids_mappings is None, f"Keep instance_to_sample_ids_mappings None when initializing PreprocessResponse"
|
|
52
57
|
|
|
53
58
|
if self.length is not None and self.sample_ids is None:
|
|
54
|
-
self.sample_ids = [i for i in range(self.length)]
|
|
59
|
+
self.sample_ids = cast(List[SampleId], [i for i in range(self.length)])
|
|
55
60
|
self.sample_id_type = int
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if self.
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
self._grouped = False
|
|
62
|
+
elif self.sample_ids is not None:
|
|
63
|
+
given_length = self.length
|
|
64
|
+
self._grouped = len(self.sample_ids) > 0 and isinstance(self.sample_ids[0], list)
|
|
65
|
+
if self._grouped:
|
|
66
|
+
groups = cast(List[List[SampleId]], self.sample_ids)
|
|
67
|
+
assert len(groups) >= 1, "Grouped PreprocessResponse must have at least one group."
|
|
68
|
+
for group in groups:
|
|
69
|
+
assert isinstance(group, list) and len(group) > 0, \
|
|
70
|
+
"Each group in a grouped PreprocessResponse must be a non-empty list."
|
|
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__}"
|
|
74
|
+
for group in groups:
|
|
75
|
+
for sample_id in group:
|
|
76
|
+
assert isinstance(sample_id, self.sample_id_type), \
|
|
77
|
+
f"Sample id should be of type {self.sample_id_type.__name__}. Got: {type(sample_id)}"
|
|
78
|
+
self.length = sum(len(group) for group in groups)
|
|
79
|
+
else:
|
|
80
|
+
flat_ids = cast(List[SampleId], self.sample_ids)
|
|
81
|
+
self.length = len(flat_ids)
|
|
82
|
+
if self.sample_id_type is None:
|
|
83
|
+
self.sample_id_type = str
|
|
84
|
+
if self.sample_id_type == str:
|
|
85
|
+
for sample_id in flat_ids:
|
|
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}."
|
|
63
92
|
else:
|
|
64
93
|
raise Exception("length is deprecated, please use sample_ids instead.")
|
|
65
94
|
|
|
@@ -79,9 +108,36 @@ class PreprocessResponse:
|
|
|
79
108
|
return id(self)
|
|
80
109
|
|
|
81
110
|
def __len__(self) -> int:
|
|
111
|
+
assert self.length is not None
|
|
112
|
+
return self.length
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def is_grouped(self) -> bool:
|
|
116
|
+
return self._grouped
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def num_groups(self) -> int:
|
|
120
|
+
# Grouped: the number of groups. Flat: the (flat) sample count, since sample_ids is the
|
|
121
|
+
# flat id list and there is one sample per "group".
|
|
82
122
|
assert self.sample_ids is not None
|
|
83
123
|
return len(self.sample_ids)
|
|
84
124
|
|
|
125
|
+
@property
|
|
126
|
+
def groups(self) -> Optional[List[List[SampleId]]]:
|
|
127
|
+
# The nested list of groups when grouped, otherwise None. Callers should
|
|
128
|
+
# branch on is_grouped before relying on this.
|
|
129
|
+
return cast(List[List[SampleId]], self.sample_ids) if self._grouped else None
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def flat_sample_ids(self) -> List[SampleId]:
|
|
133
|
+
# All sample ids in order: concatenation of the groups when grouped,
|
|
134
|
+
# otherwise the flat sample_ids list as-is.
|
|
135
|
+
assert self.sample_ids is not None
|
|
136
|
+
if self._grouped:
|
|
137
|
+
groups = cast(List[List[SampleId]], self.sample_ids)
|
|
138
|
+
return [sample_id for group in groups for sample_id in group]
|
|
139
|
+
return cast(List[SampleId], self.sample_ids)
|
|
140
|
+
|
|
85
141
|
@dataclass
|
|
86
142
|
class ElementInstance:
|
|
87
143
|
name: str
|
|
@@ -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
|
|
@@ -549,7 +572,9 @@ class LeapBinder:
|
|
|
549
572
|
# Builtin chain metadata, declared at parse time so it survives the reporter's
|
|
550
573
|
# metadata type mapping; the placeholder values are overwritten by the engine when a
|
|
551
574
|
# chain finalizes (realized length, truncated-by-safety-cap flag).
|
|
552
|
-
def builtin_chain_metadata(idx: Any, preprocess: PreprocessResponse) ->
|
|
575
|
+
def builtin_chain_metadata(idx: Any, preprocess: PreprocessResponse) -> Any:
|
|
576
|
+
if isinstance(idx, list):
|
|
577
|
+
return [{'length': -1, 'truncated': False} for _ in idx]
|
|
553
578
|
return {'length': -1, 'truncated': False}
|
|
554
579
|
|
|
555
580
|
self.set_metadata(builtin_chain_metadata, 'builtin_chain')
|
|
@@ -713,6 +738,15 @@ class LeapBinder:
|
|
|
713
738
|
if state_enum in preprocess_result_dict:
|
|
714
739
|
preprocess_result_dict_in_correct_order[state_enum] = preprocess_result_dict[state_enum]
|
|
715
740
|
|
|
741
|
+
# All-or-none: every state must agree on grouped (nested sample_ids) vs flat.
|
|
742
|
+
if len({r.is_grouped for r in preprocess_result_dict_in_correct_order.values()}) > 1:
|
|
743
|
+
shapes = ', '.join(
|
|
744
|
+
f"{state.name}={'grouped' if r.is_grouped else 'flat'}"
|
|
745
|
+
for state, r in preprocess_result_dict_in_correct_order.items())
|
|
746
|
+
raise Exception(
|
|
747
|
+
"All preprocess states must be either all grouped (nested sample_ids) or all flat, "
|
|
748
|
+
f"but got a mix: {shapes}.")
|
|
749
|
+
|
|
716
750
|
return preprocess_result_dict_in_correct_order
|
|
717
751
|
|
|
718
752
|
def get_preprocess_unlabeled_result(self) -> Optional[PreprocessResponse]:
|
|
@@ -733,7 +767,34 @@ class LeapBinder:
|
|
|
733
767
|
preprocess_response: PreprocessResponse, test_result: List[DatasetTestResultPayload],
|
|
734
768
|
dataset_base_handler: Union[DatasetBaseHandler, MetadataHandler], state: DataStateEnum) -> List[DatasetTestResultPayload]:
|
|
735
769
|
assert preprocess_response.sample_ids is not None
|
|
736
|
-
|
|
770
|
+
opened_files: Optional[Set[str]] = None
|
|
771
|
+
if preprocess_response.is_grouped:
|
|
772
|
+
# Grouped: probe with the first group, then reduce to a single sample's result so the
|
|
773
|
+
# recorded shape/type is per-sample (matching the flat case), not the (B, *) batch.
|
|
774
|
+
# (casts: sample_ids[0] is a group list here, and the grouped result is a per-sample list.)
|
|
775
|
+
group = preprocess_response.sample_ids[0]
|
|
776
|
+
with _track_opened_files() as opened_files:
|
|
777
|
+
raw_result = cast(Any, dataset_base_handler.function(cast(Any, group), preprocess_response))[0]
|
|
778
|
+
else:
|
|
779
|
+
raw_result = dataset_base_handler.function(
|
|
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
|
+
|
|
737
798
|
handler_type = 'metadata' if isinstance(dataset_base_handler, MetadataHandler) else None
|
|
738
799
|
if isinstance(dataset_base_handler, MetadataHandler):
|
|
739
800
|
if isinstance(raw_result, dict):
|
|
@@ -773,6 +834,7 @@ class LeapBinder:
|
|
|
773
834
|
else:
|
|
774
835
|
if raw_result is None:
|
|
775
836
|
if state != DataStateEnum.training:
|
|
837
|
+
_warn_if_group_spans_multiple_files(test_result)
|
|
776
838
|
return test_result
|
|
777
839
|
|
|
778
840
|
if dataset_base_handler.metadata_type is None:
|
|
@@ -794,6 +856,7 @@ class LeapBinder:
|
|
|
794
856
|
# setting shape in setup for all encoders
|
|
795
857
|
if isinstance(dataset_base_handler, (InputHandler, GroundTruthHandler)):
|
|
796
858
|
dataset_base_handler.shape = result_shape
|
|
859
|
+
_warn_if_group_spans_multiple_files(test_result)
|
|
797
860
|
return test_result
|
|
798
861
|
|
|
799
862
|
def check_handlers(self, preprocess_result: Dict[DataStateEnum, PreprocessResponse]) -> None:
|
|
@@ -829,10 +892,15 @@ class LeapBinder:
|
|
|
829
892
|
preprocess_response.tl_generated = True
|
|
830
893
|
if not preprocess_response.length or preprocess_response.length < 1:
|
|
831
894
|
raise Exception("Simulation '{}' returned PreprocessResponse with length < 1".format(sim.name))
|
|
832
|
-
preprocess_response.
|
|
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))
|
|
899
|
+
preprocess_response.sample_ids = cast(List[Union[int, str]], [0])
|
|
900
|
+
sim_sample_ids = cast(List[Union[int, str]], preprocess_response.sample_ids)
|
|
833
901
|
for handler in self.setup_container.inputs:
|
|
834
|
-
out1 = handler.function(
|
|
835
|
-
out2 = handler.function(
|
|
902
|
+
out1 = handler.function(sim_sample_ids[0], preprocess_response)
|
|
903
|
+
out2 = handler.function(sim_sample_ids[0], preprocess_response)
|
|
836
904
|
if not np.allclose(out1, out2):
|
|
837
905
|
raise Exception(
|
|
838
906
|
"Simulation '{}': encoder '{}' is non-deterministic — consecutive calls with seed=0 returned different outputs".format(
|
|
@@ -40,6 +40,7 @@ _called_from_inside_tl_integration_test_decorator = False
|
|
|
40
40
|
# to tell a raise in the test body (report as code-flow failure) from a module-level crash
|
|
41
41
|
# before any test ran (report as a plain script crash).
|
|
42
42
|
_integration_test_started = False
|
|
43
|
+
_mapping_dataset_is_grouped = False
|
|
43
44
|
_call_from_tl_platform = os.environ.get('IS_TENSORLEAP_PLATFORM') == 'true'
|
|
44
45
|
|
|
45
46
|
# ---- warnings store (module-level) ----
|
|
@@ -213,6 +214,53 @@ def batch_warning(result, func_name):
|
|
|
213
214
|
)
|
|
214
215
|
|
|
215
216
|
|
|
217
|
+
def _validate_id_or_group(sample_id, preprocess_response, func_name):
|
|
218
|
+
"""Accept either a single sample id (flat dataset) or a group — a list of ids (grouped
|
|
219
|
+
dataset). The argument shape must match the dataset: a group requires a grouped
|
|
220
|
+
PreprocessResponse and a single id requires a flat one. Every id must match the declared
|
|
221
|
+
sample_id_type."""
|
|
222
|
+
is_group = isinstance(sample_id, list)
|
|
223
|
+
if is_group and not preprocess_response.is_grouped:
|
|
224
|
+
raise AssertionError(
|
|
225
|
+
f'{func_name}() validation failed: got a group (list of sample ids) but the '
|
|
226
|
+
f'PreprocessResponse is not grouped. Pass a single sample id for a flat dataset.')
|
|
227
|
+
if not is_group and preprocess_response.is_grouped:
|
|
228
|
+
raise AssertionError(
|
|
229
|
+
f'{func_name}() validation failed: got a single sample id but the PreprocessResponse '
|
|
230
|
+
f'is grouped. Pass a group (list of sample ids) for a grouped dataset.')
|
|
231
|
+
ids = sample_id if is_group else [sample_id]
|
|
232
|
+
for sid in ids:
|
|
233
|
+
assert type(sid) == preprocess_response.sample_id_type, \
|
|
234
|
+
(f'{func_name}() validation failed: '
|
|
235
|
+
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
236
|
+
f'{preprocess_response.sample_id_type}. Got {type(sid)}.')
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _validate_grouped_result(result, group_size, func_name, validate_single):
|
|
240
|
+
"""A grouped (group) encoder result must be either a single ndarray with leading dim
|
|
241
|
+
== group_size, or a list of `group_size` per-sample results (each validated by
|
|
242
|
+
validate_single). Metadata validates its grouped result separately (list only).
|
|
243
|
+
An `(B, *)` array is validated per-sample slice (not as the whole batched array) so
|
|
244
|
+
per-sample checks (e.g. channel_dim <= rank) use the sample shape, not the batch shape."""
|
|
245
|
+
if isinstance(result, np.ndarray):
|
|
246
|
+
assert len(result.shape) > 0 and result.shape[0] == group_size, \
|
|
247
|
+
(f'{func_name}() validation failed: expected a grouped output for a group of '
|
|
248
|
+
f'{group_size}: an array with leading dim {group_size}, got shape {result.shape}.')
|
|
249
|
+
for single in result:
|
|
250
|
+
validate_single(single)
|
|
251
|
+
elif isinstance(result, list):
|
|
252
|
+
assert len(result) == group_size, \
|
|
253
|
+
(f'{func_name}() validation failed: expected a grouped output for a group of '
|
|
254
|
+
f'{group_size}: a list of {group_size} arrays, got {len(result)}.')
|
|
255
|
+
for r in result:
|
|
256
|
+
validate_single(r)
|
|
257
|
+
else:
|
|
258
|
+
raise AssertionError(
|
|
259
|
+
f'{func_name}() validation failed: expected a grouped output for a group of '
|
|
260
|
+
f'{group_size}: either an array with leading dim {group_size} or a list of '
|
|
261
|
+
f'{group_size} arrays, got {type(result)}.')
|
|
262
|
+
|
|
263
|
+
|
|
216
264
|
def _add_mapping_connection(user_unique_name, connection_destinations, arg_names, name, node_mapping_type):
|
|
217
265
|
connection_destinations = [connection_destination for connection_destination in connection_destinations
|
|
218
266
|
if not isinstance(connection_destination, SamplePreprocessResponse)]
|
|
@@ -290,11 +338,7 @@ def tensorleap_integration_test():
|
|
|
290
338
|
|
|
291
339
|
def _validate_input_args(*args, **kwargs):
|
|
292
340
|
sample_id, preprocess_response = args
|
|
293
|
-
|
|
294
|
-
f"tensorleap_integration_test validation failed: "
|
|
295
|
-
f"sample_id type ({type(sample_id).__name__}) does not match the expected "
|
|
296
|
-
f"type ({preprocess_response.sample_id_type}) from the PreprocessResponse."
|
|
297
|
-
)
|
|
341
|
+
_validate_id_or_group(sample_id, preprocess_response, 'tensorleap_integration_test')
|
|
298
342
|
|
|
299
343
|
def inner(*args, **kwargs):
|
|
300
344
|
if not _call_from_tl_platform:
|
|
@@ -305,7 +349,7 @@ def tensorleap_integration_test():
|
|
|
305
349
|
expected_names = inspect.getfullargspec(integration_test_function)[0][:2]
|
|
306
350
|
if len(expected_names) < 2:
|
|
307
351
|
expected_names = ['idx', 'preprocess']
|
|
308
|
-
validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
|
|
352
|
+
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
309
353
|
func_name='integration_test', expected_names=expected_names,
|
|
310
354
|
**kwargs)
|
|
311
355
|
sample_id, preprocess_response = (
|
|
@@ -329,6 +373,8 @@ def tensorleap_integration_test():
|
|
|
329
373
|
_reset_model_loop_state()
|
|
330
374
|
ret = integration_test_function(*args, **kwargs)
|
|
331
375
|
|
|
376
|
+
global _mapping_dataset_is_grouped
|
|
377
|
+
_mapping_dataset_is_grouped = args[1].is_grouped
|
|
332
378
|
try:
|
|
333
379
|
os.environ[mapping_runtime_mode_env_var_mame] = 'True'
|
|
334
380
|
_reset_model_loop_state()
|
|
@@ -349,6 +395,7 @@ def tensorleap_integration_test():
|
|
|
349
395
|
f'Integration test is only allowed to call Tensorleap decorators. '
|
|
350
396
|
f'Ensure any arithmetics, external library use, Python logic is placed within Tensorleap decoders')
|
|
351
397
|
finally:
|
|
398
|
+
_mapping_dataset_is_grouped = False
|
|
352
399
|
if mapping_runtime_mode_env_var_mame in os.environ:
|
|
353
400
|
del os.environ[mapping_runtime_mode_env_var_mame]
|
|
354
401
|
finally:
|
|
@@ -639,10 +686,29 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
639
686
|
|
|
640
687
|
class ModelPlaceholder:
|
|
641
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
|
+
|
|
642
706
|
# keras interface
|
|
643
707
|
def __call__(self, arg):
|
|
644
708
|
if isinstance(arg, list):
|
|
709
|
+
seen: set = set()
|
|
645
710
|
for i, elem in enumerate(arg):
|
|
711
|
+
self._reject_reused_input_source(elem, seen)
|
|
646
712
|
elem.node_mapping.type = _safe_get_item(i)
|
|
647
713
|
else:
|
|
648
714
|
arg.node_mapping.type = NodeMappingType.Input0
|
|
@@ -656,7 +722,9 @@ def tensorleap_load_model(prediction_types: Optional[List[PredictionTypeHandler]
|
|
|
656
722
|
"use model.run(None, inputs) to return all outputs.")
|
|
657
723
|
assert isinstance(input_dict, dict), \
|
|
658
724
|
f'Expected input_dict to be a dict, got {type(input_dict)} instead.'
|
|
725
|
+
seen: set = set()
|
|
659
726
|
for i, (input_key, elem) in enumerate(input_dict.items()):
|
|
727
|
+
self._reject_reused_input_source(elem, seen)
|
|
660
728
|
if isinstance(input_key, NodeMappingType):
|
|
661
729
|
elem.node_mapping.type = input_key
|
|
662
730
|
else:
|
|
@@ -1561,13 +1629,10 @@ def tensorleap_metadata(
|
|
|
1561
1629
|
raise Exception(f'Metadata with name {name} already exists. '
|
|
1562
1630
|
f'Please choose another')
|
|
1563
1631
|
|
|
1564
|
-
def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
|
|
1565
|
-
|
|
1566
|
-
(f'{user_function.__name__}() validation failed: '
|
|
1567
|
-
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
1568
|
-
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
1632
|
+
def _validate_input_args(sample_id: Union[int, str, list], preprocess_response: PreprocessResponse):
|
|
1633
|
+
_validate_id_or_group(sample_id, preprocess_response, user_function.__name__)
|
|
1569
1634
|
|
|
1570
|
-
def
|
|
1635
|
+
def _validate_single(result):
|
|
1571
1636
|
supported_result_types = (type(None), int, str, bool, float, dict, np.floating,
|
|
1572
1637
|
np.bool_, np.unsignedinteger, np.signedinteger, np.integer)
|
|
1573
1638
|
if isinstance(result, tuple):
|
|
@@ -1585,6 +1650,20 @@ def tensorleap_metadata(
|
|
|
1585
1650
|
(f'{user_function.__name__}() validation failed: '
|
|
1586
1651
|
f'Values in the return dict should be of type {str(supported_result_types)}. Got {type(value)}.')
|
|
1587
1652
|
|
|
1653
|
+
def _validate_result(result, grouped=False, group_size=None):
|
|
1654
|
+
if not grouped:
|
|
1655
|
+
_validate_single(result)
|
|
1656
|
+
return
|
|
1657
|
+
# Grouped metadata: a list of `group_size` per-sample scalars/dicts.
|
|
1658
|
+
assert isinstance(result, list), \
|
|
1659
|
+
(f'{user_function.__name__}() validation failed: expected a grouped output for a group '
|
|
1660
|
+
f'of {group_size}: a list of {group_size} metadata values, got {type(result)}.')
|
|
1661
|
+
assert len(result) == group_size, \
|
|
1662
|
+
(f'{user_function.__name__}() validation failed: expected a grouped output for a group '
|
|
1663
|
+
f'of {group_size}: a list of {group_size} metadata values, got {len(result)}.')
|
|
1664
|
+
for value in result:
|
|
1665
|
+
_validate_single(value)
|
|
1666
|
+
|
|
1588
1667
|
def inner_without_validate(sample_id, preprocess_response):
|
|
1589
1668
|
|
|
1590
1669
|
global _called_from_inside_tl_decorator
|
|
@@ -1604,14 +1683,16 @@ def tensorleap_metadata(
|
|
|
1604
1683
|
set_current('tensorleap_metadata')
|
|
1605
1684
|
if os.environ.get(mapping_runtime_mode_env_var_mame):
|
|
1606
1685
|
return None
|
|
1607
|
-
validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
|
|
1686
|
+
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
1608
1687
|
func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
|
|
1609
1688
|
sample_id, preprocess_response = args if len(args) != 0 else kwargs.values()
|
|
1610
1689
|
_validate_input_args(sample_id, preprocess_response)
|
|
1611
1690
|
|
|
1691
|
+
grouped = isinstance(sample_id, list)
|
|
1692
|
+
group_size = len(sample_id) if grouped else None
|
|
1612
1693
|
result = inner_without_validate(sample_id, preprocess_response)
|
|
1613
1694
|
|
|
1614
|
-
_validate_result(result)
|
|
1695
|
+
_validate_result(result, grouped, group_size)
|
|
1615
1696
|
if not _call_from_tl_platform:
|
|
1616
1697
|
update_env_params_func("tensorleap_metadata", "v")
|
|
1617
1698
|
return result
|
|
@@ -1623,35 +1704,50 @@ def tensorleap_metadata(
|
|
|
1623
1704
|
|
|
1624
1705
|
def tensorleap_custom_latent_space():
|
|
1625
1706
|
def decorating_function(user_function: SectionCallableInterface):
|
|
1626
|
-
def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
|
|
1627
|
-
|
|
1628
|
-
(f'tensorleap_custom_latent_space validation failed: '
|
|
1629
|
-
f'Argument sample_id should be either int or str. Got {type(sample_id)}.')
|
|
1630
|
-
assert isinstance(preprocess_response, PreprocessResponse), \
|
|
1631
|
-
(f'tensorleap_custom_latent_space validation failed: '
|
|
1632
|
-
f'Argument preprocess_response should be a PreprocessResponse. Got {type(preprocess_response)}.')
|
|
1633
|
-
assert type(sample_id) == preprocess_response.sample_id_type, \
|
|
1634
|
-
(f'tensorleap_custom_latent_space validation failed: '
|
|
1635
|
-
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
1636
|
-
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
1707
|
+
def _validate_input_args(sample_id: Union[int, str, list], preprocess_response: PreprocessResponse):
|
|
1708
|
+
_validate_id_or_group(sample_id, preprocess_response, 'tensorleap_custom_latent_space')
|
|
1637
1709
|
|
|
1638
|
-
def
|
|
1639
|
-
assert isinstance(
|
|
1710
|
+
def _validate_single(single_result):
|
|
1711
|
+
assert isinstance(single_result, np.ndarray), \
|
|
1640
1712
|
(f'tensorleap_custom_latent_space validation failed: '
|
|
1641
|
-
f'The return type should be a numpy array. Got {type(
|
|
1642
|
-
if
|
|
1643
|
-
flat_dim = int(np.prod(
|
|
1713
|
+
f'The return type should be a numpy array. Got {type(single_result)}.')
|
|
1714
|
+
if single_result.ndim > 1:
|
|
1715
|
+
flat_dim = int(np.prod(single_result.shape))
|
|
1644
1716
|
store_general_warning(
|
|
1645
|
-
key=("tensorleap_custom_latent_space_flatten", tuple(
|
|
1717
|
+
key=("tensorleap_custom_latent_space_flatten", tuple(single_result.shape)),
|
|
1646
1718
|
message=(
|
|
1647
|
-
f"tensorleap_custom_latent_space returned per-sample shape {tuple(
|
|
1648
|
-
f"(ndim={
|
|
1719
|
+
f"tensorleap_custom_latent_space returned per-sample shape {tuple(single_result.shape)} "
|
|
1720
|
+
f"(ndim={single_result.ndim}). Tensorleap assumes per-sample shape (d, ...) and will "
|
|
1649
1721
|
f"flatten to ({flat_dim},) before downstream visualization and clustering. "
|
|
1650
1722
|
f"If you want a different aggregation (e.g. global average pooling), do it "
|
|
1651
1723
|
f"inside your function."
|
|
1652
1724
|
),
|
|
1653
1725
|
)
|
|
1654
1726
|
|
|
1727
|
+
def _validate_result(result, grouped=False, group_size=None):
|
|
1728
|
+
if not grouped:
|
|
1729
|
+
_validate_single(result)
|
|
1730
|
+
return
|
|
1731
|
+
# Grouped: (B, *) array or list of B per-sample arrays. Validate each per-sample slice so
|
|
1732
|
+
# the per-sample flatten check/warning uses the sample shape, not the (B, *) batch shape.
|
|
1733
|
+
if isinstance(result, np.ndarray):
|
|
1734
|
+
assert result.ndim >= 1 and result.shape[0] == group_size, \
|
|
1735
|
+
(f'tensorleap_custom_latent_space validation failed: expected a grouped output for a '
|
|
1736
|
+
f'group of {group_size}: an array with leading dim {group_size}, got shape {result.shape}.')
|
|
1737
|
+
for single in result:
|
|
1738
|
+
_validate_single(single)
|
|
1739
|
+
elif isinstance(result, list):
|
|
1740
|
+
assert len(result) == group_size, \
|
|
1741
|
+
(f'tensorleap_custom_latent_space validation failed: expected a grouped output for a '
|
|
1742
|
+
f'group of {group_size}: a list of {group_size} arrays, got {len(result)}.')
|
|
1743
|
+
for single in result:
|
|
1744
|
+
_validate_single(single)
|
|
1745
|
+
else:
|
|
1746
|
+
raise AssertionError(
|
|
1747
|
+
f'tensorleap_custom_latent_space validation failed: expected a grouped output for a '
|
|
1748
|
+
f'group of {group_size}: either an array with leading dim {group_size} or a list of '
|
|
1749
|
+
f'{group_size} arrays, got {type(result)}.')
|
|
1750
|
+
|
|
1655
1751
|
def inner_without_validate(sample_id, preprocess_response):
|
|
1656
1752
|
global _called_from_inside_tl_decorator
|
|
1657
1753
|
_called_from_inside_tl_decorator += 1
|
|
@@ -1671,9 +1767,11 @@ def tensorleap_custom_latent_space():
|
|
|
1671
1767
|
|
|
1672
1768
|
_validate_input_args(sample_id, preprocess_response)
|
|
1673
1769
|
|
|
1770
|
+
grouped = isinstance(sample_id, list)
|
|
1771
|
+
group_size = len(sample_id) if grouped else None
|
|
1674
1772
|
result = inner_without_validate(sample_id, preprocess_response)
|
|
1675
1773
|
|
|
1676
|
-
_validate_result(result)
|
|
1774
|
+
_validate_result(result, grouped, group_size)
|
|
1677
1775
|
return result
|
|
1678
1776
|
|
|
1679
1777
|
return inner
|
|
@@ -2642,6 +2740,15 @@ def tensorleap_preprocess():
|
|
|
2642
2740
|
assert len(set(result)) == len(result), \
|
|
2643
2741
|
(f'{user_function.__name__}() validation failed: '
|
|
2644
2742
|
f'The return list should not contain duplicate PreprocessResponse objects.')
|
|
2743
|
+
# All-or-none: a dataset is either all grouped (nested sample_ids) or all flat.
|
|
2744
|
+
if len({response.is_grouped for response in result}) != 1:
|
|
2745
|
+
shapes = ', '.join(
|
|
2746
|
+
f"#{i}={'grouped' if response.is_grouped else 'flat'}"
|
|
2747
|
+
for i, response in enumerate(result))
|
|
2748
|
+
raise AssertionError(
|
|
2749
|
+
f'{user_function.__name__}() validation failed: '
|
|
2750
|
+
f'All PreprocessResponses must be either all grouped (nested sample_ids) '
|
|
2751
|
+
f'or all flat, but got a mix: {shapes}.')
|
|
2645
2752
|
|
|
2646
2753
|
def inner(*args, **kwargs):
|
|
2647
2754
|
if not _call_from_tl_platform:
|
|
@@ -2979,14 +3086,10 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
2979
3086
|
f"channel axis in the batched tensor (batch = axis 0), so 0 is invalid. Use 1 "
|
|
2980
3087
|
f"for channel-first (NCHW) and -1 for channel-last (NHWC).")
|
|
2981
3088
|
|
|
2982
|
-
def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
|
|
2983
|
-
|
|
2984
|
-
(f'{user_function.__name__}() validation failed: '
|
|
2985
|
-
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
2986
|
-
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
3089
|
+
def _validate_input_args(sample_id: Union[int, str, list], preprocess_response: PreprocessResponse):
|
|
3090
|
+
_validate_id_or_group(sample_id, preprocess_response, user_function.__name__)
|
|
2987
3091
|
|
|
2988
|
-
def
|
|
2989
|
-
validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray")
|
|
3092
|
+
def _validate_single(result):
|
|
2990
3093
|
assert isinstance(result, np.ndarray), \
|
|
2991
3094
|
(f'{user_function.__name__}() validation failed: '
|
|
2992
3095
|
f'Unsupported return type. Should be a numpy array. Got {type(result)}.')
|
|
@@ -2996,6 +3099,13 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
2996
3099
|
assert channel_dim - 1 <= len(result.shape), (f'{user_function.__name__}() validation failed: '
|
|
2997
3100
|
f'The channel_dim ({channel_dim}) should be <= to the rank of the resulting input rank ({len(result.shape)}).')
|
|
2998
3101
|
|
|
3102
|
+
def _validate_result(result, grouped=False, group_size=None):
|
|
3103
|
+
if not grouped:
|
|
3104
|
+
validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray")
|
|
3105
|
+
_validate_single(result)
|
|
3106
|
+
return
|
|
3107
|
+
_validate_grouped_result(result, group_size, user_function.__name__, _validate_single)
|
|
3108
|
+
|
|
2999
3109
|
def inner_without_validate(sample_id, preprocess_response):
|
|
3000
3110
|
global _called_from_inside_tl_decorator
|
|
3001
3111
|
_called_from_inside_tl_decorator += 1
|
|
@@ -3012,18 +3122,29 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
3012
3122
|
def inner(*args, **kwargs):
|
|
3013
3123
|
if not _call_from_tl_platform:
|
|
3014
3124
|
set_current("tensorleap_input_encoder")
|
|
3015
|
-
validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
|
|
3125
|
+
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
3016
3126
|
func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
|
|
3017
3127
|
sample_id, preprocess_response = args if len(args) != 0 else kwargs.values()
|
|
3018
3128
|
_validate_input_args(sample_id, preprocess_response)
|
|
3019
3129
|
|
|
3130
|
+
grouped = isinstance(sample_id, list)
|
|
3131
|
+
group_size = len(sample_id) if grouped else None
|
|
3020
3132
|
result = inner_without_validate(sample_id, preprocess_response)
|
|
3021
3133
|
|
|
3022
|
-
_validate_result(result)
|
|
3134
|
+
_validate_result(result, grouped, group_size)
|
|
3023
3135
|
|
|
3024
3136
|
if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
|
|
3025
|
-
|
|
3026
|
-
|
|
3137
|
+
if grouped:
|
|
3138
|
+
# A grouped result is a list of per-sample arrays (never stacked, so a
|
|
3139
|
+
# B=1-only model can be fed one sample at a time). A user may also return a
|
|
3140
|
+
# supported (B, *) array; split it back into per-sample rows first (mirrors
|
|
3141
|
+
# LeapLoader._to_grouped_list) so both forms match the runtime path. Then add
|
|
3142
|
+
# a batch dim to each sample, mirroring the flat path's single-sample expand_dims.
|
|
3143
|
+
rows = result if isinstance(result, list) else [row for row in np.asarray(result)]
|
|
3144
|
+
result = [np.expand_dims(r, axis=0) for r in rows]
|
|
3145
|
+
else:
|
|
3146
|
+
batch_warning(result, user_function.__name__)
|
|
3147
|
+
result = np.expand_dims(result, axis=0)
|
|
3027
3148
|
# Emit integration test event once per test
|
|
3028
3149
|
try:
|
|
3029
3150
|
emit_integration_event_once(AnalyticsEvent.INPUT_ENCODER_INTEGRATION_TEST, {
|
|
@@ -3044,8 +3165,15 @@ def tensorleap_input_encoder(name: str, channel_dim=_UNSET, model_input_index=No
|
|
|
3044
3165
|
inner.node_mapping = NodeMapping(name, node_mapping_type)
|
|
3045
3166
|
|
|
3046
3167
|
def mapping_inner(*args, **kwargs):
|
|
3047
|
-
|
|
3048
|
-
|
|
3168
|
+
if _mapping_dataset_is_grouped:
|
|
3169
|
+
class TempMapping:
|
|
3170
|
+
def __getitem__(self, key):
|
|
3171
|
+
# Indexing a grouped input group (input_list[i]) selects a sample from the
|
|
3172
|
+
# same input source; the model wiring is identical to the un-grouped input.
|
|
3173
|
+
return self
|
|
3174
|
+
else:
|
|
3175
|
+
class TempMapping:
|
|
3176
|
+
pass
|
|
3049
3177
|
|
|
3050
3178
|
ret = TempMapping()
|
|
3051
3179
|
ret.node_mapping = mapping_inner.node_mapping
|
|
@@ -3075,15 +3203,10 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3075
3203
|
raise Exception(f'GT with name {name} already exists. '
|
|
3076
3204
|
f'Please choose another')
|
|
3077
3205
|
|
|
3078
|
-
def _validate_input_args(sample_id: Union[int, str], preprocess_response: PreprocessResponse):
|
|
3079
|
-
|
|
3080
|
-
(f'{user_function.__name__}() validation failed: '
|
|
3081
|
-
f'Argument sample_id should be as the same type as defined in the preprocess response '
|
|
3082
|
-
f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
|
|
3206
|
+
def _validate_input_args(sample_id: Union[int, str, list], preprocess_response: PreprocessResponse):
|
|
3207
|
+
_validate_id_or_group(sample_id, preprocess_response, user_function.__name__)
|
|
3083
3208
|
|
|
3084
|
-
def
|
|
3085
|
-
validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray",
|
|
3086
|
-
gt_flag=True)
|
|
3209
|
+
def _validate_single(result):
|
|
3087
3210
|
assert isinstance(result, np.ndarray), \
|
|
3088
3211
|
(f'{user_function.__name__}() validation failed: '
|
|
3089
3212
|
f'Unsupported return type. Should be a numpy array. Got {type(result)}.')
|
|
@@ -3091,6 +3214,14 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3091
3214
|
(f'{user_function.__name__}() validation failed: '
|
|
3092
3215
|
f'The return type should be a numpy array of type float32. Got {result.dtype}.')
|
|
3093
3216
|
|
|
3217
|
+
def _validate_result(result, grouped=False, group_size=None):
|
|
3218
|
+
if not grouped:
|
|
3219
|
+
validate_output_structure(result, func_name=user_function.__name__, expected_type_name="np.ndarray",
|
|
3220
|
+
gt_flag=True)
|
|
3221
|
+
_validate_single(result)
|
|
3222
|
+
return
|
|
3223
|
+
_validate_grouped_result(result, group_size, user_function.__name__, _validate_single)
|
|
3224
|
+
|
|
3094
3225
|
def inner_without_validate(sample_id, preprocess_response):
|
|
3095
3226
|
global _called_from_inside_tl_decorator
|
|
3096
3227
|
_called_from_inside_tl_decorator += 1
|
|
@@ -3107,18 +3238,29 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3107
3238
|
def inner(*args, **kwargs):
|
|
3108
3239
|
if not _call_from_tl_platform:
|
|
3109
3240
|
set_current("tensorleap_gt_encoder")
|
|
3110
|
-
validate_args_structure(*args, types_order=[Union[int, str], PreprocessResponse],
|
|
3241
|
+
validate_args_structure(*args, types_order=[Union[int, str, list], PreprocessResponse],
|
|
3111
3242
|
func_name=user_function.__name__, expected_names=["idx", "preprocess"], **kwargs)
|
|
3112
|
-
sample_id, preprocess_response = args
|
|
3243
|
+
sample_id, preprocess_response = args if len(args) != 0 else kwargs.values()
|
|
3113
3244
|
_validate_input_args(sample_id, preprocess_response)
|
|
3114
3245
|
|
|
3246
|
+
grouped = isinstance(sample_id, list)
|
|
3247
|
+
group_size = len(sample_id) if grouped else None
|
|
3115
3248
|
result = inner_without_validate(sample_id, preprocess_response)
|
|
3116
3249
|
|
|
3117
|
-
_validate_result(result)
|
|
3250
|
+
_validate_result(result, grouped, group_size)
|
|
3118
3251
|
|
|
3119
3252
|
if _called_from_inside_tl_decorator == 0 and _called_from_inside_tl_integration_test_decorator:
|
|
3120
|
-
|
|
3121
|
-
|
|
3253
|
+
if grouped:
|
|
3254
|
+
# A grouped result is a list of per-sample arrays (never stacked, so a
|
|
3255
|
+
# B=1-only model can be fed one sample at a time). A user may also return a
|
|
3256
|
+
# supported (B, *) array; split it back into per-sample rows first (mirrors
|
|
3257
|
+
# LeapLoader._to_grouped_list) so both forms match the runtime path. Then add
|
|
3258
|
+
# a batch dim to each sample, mirroring the flat path's single-sample expand_dims.
|
|
3259
|
+
rows = result if isinstance(result, list) else [row for row in np.asarray(result)]
|
|
3260
|
+
result = [np.expand_dims(r, axis=0) for r in rows]
|
|
3261
|
+
else:
|
|
3262
|
+
batch_warning(result, user_function.__name__)
|
|
3263
|
+
result = np.expand_dims(result, axis=0)
|
|
3122
3264
|
_register_chain_artifact(result, 'ground_truth')
|
|
3123
3265
|
# Emit integration test event once per test
|
|
3124
3266
|
try:
|
|
@@ -3134,8 +3276,15 @@ def tensorleap_gt_encoder(name: str):
|
|
|
3134
3276
|
inner.node_mapping = NodeMapping(name, NodeMappingType.GroundTruth)
|
|
3135
3277
|
|
|
3136
3278
|
def mapping_inner(*args, **kwargs):
|
|
3137
|
-
|
|
3138
|
-
|
|
3279
|
+
if _mapping_dataset_is_grouped:
|
|
3280
|
+
class TempMapping:
|
|
3281
|
+
def __getitem__(self, key):
|
|
3282
|
+
# Indexing a grouped GT group (gt_list[i]) selects a sample from the same
|
|
3283
|
+
# ground-truth source; wiring is identical to the un-grouped GT node.
|
|
3284
|
+
return self
|
|
3285
|
+
else:
|
|
3286
|
+
class TempMapping:
|
|
3287
|
+
pass
|
|
3139
3288
|
|
|
3140
3289
|
ret = TempMapping()
|
|
3141
3290
|
ret.node_mapping = mapping_inner.node_mapping
|
code_loader/leaploader.py
CHANGED
|
@@ -60,14 +60,36 @@ class LeapLoader(LeapLoaderBase):
|
|
|
60
60
|
|
|
61
61
|
@lru_cache()
|
|
62
62
|
def exec_script(self) -> None:
|
|
63
|
+
from code_loader.inner_leap_binder import leapbinder_decorators as _leap_dec
|
|
63
64
|
try:
|
|
64
65
|
os.environ[mapping_runtime_mode_env_var_mame] = 'TRUE'
|
|
65
66
|
self.evaluate_module()
|
|
67
|
+
|
|
68
|
+
# A grouped integration test may index a grouped input/gt (image_list[i]) to wire a
|
|
69
|
+
# single sample of a group; that indexing is only valid when the mapping graph is
|
|
70
|
+
# built in grouped mode. Detect grouped-ness so the placeholder pass below matches
|
|
71
|
+
# the dataset. Preprocess is stubbed while mapping mode is on, so run it with mapping
|
|
72
|
+
# mode momentarily disabled, and cache it so _preprocess_result() doesn't re-run it.
|
|
73
|
+
is_grouped = False
|
|
74
|
+
if global_leap_binder.integration_test_func is not None:
|
|
75
|
+
del os.environ[mapping_runtime_mode_env_var_mame]
|
|
76
|
+
try:
|
|
77
|
+
preprocess_result = global_leap_binder.get_preprocess_result()
|
|
78
|
+
self._preprocess_result_cached = preprocess_result
|
|
79
|
+
is_grouped = any(r.is_grouped for r in preprocess_result.values())
|
|
80
|
+
finally:
|
|
81
|
+
os.environ[mapping_runtime_mode_env_var_mame] = 'TRUE'
|
|
82
|
+
|
|
83
|
+
_leap_dec._mapping_dataset_is_grouped = is_grouped
|
|
66
84
|
if global_leap_binder.integration_test_func is not None:
|
|
67
85
|
from code_loader.inner_leap_binder.leapbinder_decorators import \
|
|
68
86
|
_reset_model_loop_state
|
|
69
87
|
_reset_model_loop_state()
|
|
70
|
-
|
|
88
|
+
mapping_preprocess = (
|
|
89
|
+
PreprocessResponse(sample_ids=[["__mapping_placeholder__"]], state=DataStateType.training)
|
|
90
|
+
if is_grouped else
|
|
91
|
+
PreprocessResponse(state=DataStateType.training, length=0))
|
|
92
|
+
global_leap_binder.integration_test_func(None, mapping_preprocess)
|
|
71
93
|
except TypeError as e:
|
|
72
94
|
import traceback
|
|
73
95
|
global_leap_binder.setup_container = DatasetIntegrationSetup()
|
|
@@ -80,6 +102,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
80
102
|
raise DatasetScriptException(getattr(e, 'message', repr(e))) from e
|
|
81
103
|
finally:
|
|
82
104
|
# ensure that the environment variable is removed after the script execution
|
|
105
|
+
_leap_dec._mapping_dataset_is_grouped = False
|
|
83
106
|
if mapping_runtime_mode_env_var_mame in os.environ:
|
|
84
107
|
del os.environ[mapping_runtime_mode_env_var_mame]
|
|
85
108
|
|
|
@@ -209,7 +232,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
209
232
|
additional = self._preprocess_result().get(DataStateEnum.additional)
|
|
210
233
|
if additional is None:
|
|
211
234
|
return set()
|
|
212
|
-
return set(additional.
|
|
235
|
+
return set(additional.flat_sample_ids)
|
|
213
236
|
|
|
214
237
|
def _resolve_synthetic(self, sample_id: Union[int, str],
|
|
215
238
|
state: Optional[DataStateEnum] = None
|
|
@@ -250,16 +273,20 @@ class LeapLoader(LeapLoaderBase):
|
|
|
250
273
|
)
|
|
251
274
|
|
|
252
275
|
preprocess_result = self._preprocess_result()
|
|
253
|
-
if state == DataStateEnum.unlabeled and sample_id not in preprocess_result[state].
|
|
276
|
+
if state == DataStateEnum.unlabeled and sample_id not in preprocess_result[state].flat_sample_ids:
|
|
254
277
|
self._preprocess_result(update_unlabeled_preprocess=True)
|
|
255
278
|
|
|
256
279
|
metadata, metadata_is_none = self.get_metadata(state, sample_id)
|
|
257
280
|
|
|
258
281
|
custom_latent_space = None
|
|
259
282
|
if global_leap_binder.setup_container.custom_latent_space is not None:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
283
|
+
latent_fn = global_leap_binder.setup_container.custom_latent_space.function
|
|
284
|
+
preprocess_state = preprocess_result[state]
|
|
285
|
+
if preprocess_state.is_grouped:
|
|
286
|
+
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
287
|
+
custom_latent_space = self._to_grouped_list(latent_fn(group_ids, preprocess_state))[pos]
|
|
288
|
+
else:
|
|
289
|
+
custom_latent_space = latent_fn(sample_id, preprocess_state)
|
|
263
290
|
instance_mask = self._get_instances_masks(state, sample_id, instance_id)
|
|
264
291
|
sample = DatasetSample(inputs=self._get_inputs(state, sample_id),
|
|
265
292
|
gt=None if state == DataStateEnum.unlabeled else self._get_gt(state, sample_id),
|
|
@@ -387,10 +414,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
387
414
|
test_result = DatasetTestResultPayload('preprocess')
|
|
388
415
|
try:
|
|
389
416
|
preprocess_result = self._preprocess_result()
|
|
390
|
-
if self.get_sample_id_type()
|
|
417
|
+
if issubclass(self.get_sample_id_type(), str):
|
|
391
418
|
max_allowed_item_size = np.dtype('<U256').itemsize
|
|
392
419
|
for state, preprocess_response in preprocess_result.items():
|
|
393
|
-
sample_ids_array = np.array(preprocess_response.
|
|
420
|
+
sample_ids_array = np.array(preprocess_response.flat_sample_ids)
|
|
394
421
|
if sample_ids_array.dtype.itemsize > max_allowed_item_size:
|
|
395
422
|
raise Exception(f"Sample id are too long. Max allowed length is 256 charecters.")
|
|
396
423
|
|
|
@@ -451,6 +478,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
451
478
|
preprocess_response.tl_generated = True
|
|
452
479
|
if preprocess_response.length < 1:
|
|
453
480
|
raise ValueError("Simulation returned PreprocessResponse with length < 1")
|
|
481
|
+
if preprocess_response.is_grouped:
|
|
482
|
+
raise ValueError(
|
|
483
|
+
"Simulation returned a grouped PreprocessResponse; simulations must "
|
|
484
|
+
"return a flat (non-grouped) PreprocessResponse.")
|
|
454
485
|
preprocess_response.sample_ids = [0]
|
|
455
486
|
for handler in global_leap_binder.setup_container.inputs:
|
|
456
487
|
out1 = handler.function(preprocess_response.sample_ids[0], preprocess_response)
|
|
@@ -486,7 +517,10 @@ class LeapLoader(LeapLoaderBase):
|
|
|
486
517
|
if preprocess_response.sample_ids_to_instance_mappings:
|
|
487
518
|
raise Exception('Element instances are not supported together with '
|
|
488
519
|
'tensorleap_autoregressive_step.')
|
|
489
|
-
|
|
520
|
+
# Grouped preprocess is fine: grouping only amortizes the shared base reads
|
|
521
|
+
# (GT/metadata/custom_latent) — the AR hook always drives a single scalar chain
|
|
522
|
+
# and is never handed a group.
|
|
523
|
+
sample_id = preprocess_response.flat_sample_ids[0]
|
|
490
524
|
first_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
491
525
|
second_result = handler.function(sample_id, None, None, None, preprocess_response)
|
|
492
526
|
if not isinstance(first_result, tuple) or len(first_result) != 2:
|
|
@@ -567,7 +601,7 @@ class LeapLoader(LeapLoaderBase):
|
|
|
567
601
|
if handler.input_shapes is None:
|
|
568
602
|
preprocess_result = self._preprocess_result()
|
|
569
603
|
first_state_response = next(iter(preprocess_result.values()))
|
|
570
|
-
first_sample_id = first_state_response.
|
|
604
|
+
first_sample_id = first_state_response.flat_sample_ids[0]
|
|
571
605
|
first_result = handler.function(first_sample_id, None, None, None,
|
|
572
606
|
first_state_response)
|
|
573
607
|
if not isinstance(first_result, tuple) or len(first_result) != 2 or \
|
|
@@ -906,6 +940,57 @@ class LeapLoader(LeapLoaderBase):
|
|
|
906
940
|
|
|
907
941
|
return sample_ids
|
|
908
942
|
|
|
943
|
+
@staticmethod
|
|
944
|
+
def _grouped_dict_keys(rows: List[Dict[str, Any]], handler_name: str) -> List[str]:
|
|
945
|
+
"""Keys for a group of dict-metadata rows, requiring every row to share the same keys.
|
|
946
|
+
A group must expose a uniform metadata schema; otherwise the per-key lists would silently
|
|
947
|
+
drop or KeyError on rows that differ from row 0."""
|
|
948
|
+
keys = list(rows[0].keys())
|
|
949
|
+
key_set = set(keys)
|
|
950
|
+
for pos, row in enumerate(rows):
|
|
951
|
+
if set(row.keys()) != key_set:
|
|
952
|
+
raise ValueError(
|
|
953
|
+
f"Metadata handler {handler_name!r} returned inconsistent dict keys across the "
|
|
954
|
+
f"group: row 0 has {sorted(key_set)} but row {pos} has {sorted(row.keys())}. "
|
|
955
|
+
f"All rows in a group must share the same metadata keys.")
|
|
956
|
+
return keys
|
|
957
|
+
|
|
958
|
+
@staticmethod
|
|
959
|
+
def _to_grouped_list(raw: Any) -> List[npt.NDArray[np.float32]]:
|
|
960
|
+
"""Normalize a grouped encoder result to a list of per-sample arrays.
|
|
961
|
+
|
|
962
|
+
Grouped results are NEVER stacked into a (B, *) batch. Grouping is only a storage
|
|
963
|
+
read-unit (one file load per group); assembling the model batch (B) is the engine's
|
|
964
|
+
single responsibility, so it stays the only place samples get batched. Keeping
|
|
965
|
+
per-sample arrays lets the engine batch to any B — including a B=1-only model — without
|
|
966
|
+
an intermediate stack/unstack. A user encoder that already returned a stacked (B, *)
|
|
967
|
+
array is split back into its per-sample rows so the contract is a list either way."""
|
|
968
|
+
if isinstance(raw, list):
|
|
969
|
+
return raw
|
|
970
|
+
return [row for row in np.asarray(raw)]
|
|
971
|
+
|
|
972
|
+
def _locate_group(self, preprocess_state: PreprocessResponse,
|
|
973
|
+
sample_id: Union[int, str]) -> Tuple[List[Union[int, str]], int]:
|
|
974
|
+
"""Map a sample id to (its group, its position in that group). Cached on the
|
|
975
|
+
PreprocessResponse itself (not keyed by id() on this loader) so grouped single-sample
|
|
976
|
+
fetches don't rescan the groups, and the cache can never go stale: when the response is
|
|
977
|
+
replaced (e.g. unlabeled preprocess refresh), the old object and its cache are simply
|
|
978
|
+
discarded together instead of lingering under a possibly-reused id()."""
|
|
979
|
+
mapping = preprocess_state._group_pos_cache
|
|
980
|
+
if mapping is None:
|
|
981
|
+
mapping = {}
|
|
982
|
+
for group in preprocess_state.groups:
|
|
983
|
+
for pos, sid in enumerate(group):
|
|
984
|
+
if sid in mapping:
|
|
985
|
+
raise ValueError(
|
|
986
|
+
f"Duplicate sample id {sid!r} across groups in the preprocess response; "
|
|
987
|
+
f"sample ids must be unique within and across groups.")
|
|
988
|
+
mapping[sid] = (group, pos)
|
|
989
|
+
preprocess_state._group_pos_cache = mapping
|
|
990
|
+
if sample_id not in mapping:
|
|
991
|
+
raise KeyError(f"Sample id {sample_id!r} is not present in any group of this preprocess response.")
|
|
992
|
+
return mapping[sample_id]
|
|
993
|
+
|
|
909
994
|
def _get_dataset_handlers(self, handlers: Iterable[DatasetBaseHandler],
|
|
910
995
|
state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
911
996
|
result_agg = {}
|
|
@@ -917,12 +1002,101 @@ class LeapLoader(LeapLoaderBase):
|
|
|
917
1002
|
sample_id = original_local_id
|
|
918
1003
|
else:
|
|
919
1004
|
preprocess_state = preprocess_result[state]
|
|
1005
|
+
if preprocess_state.is_grouped:
|
|
1006
|
+
# Grouped dataset: call the encoder once with the whole group, index out the sample.
|
|
1007
|
+
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
1008
|
+
for handler in handlers:
|
|
1009
|
+
grouped = self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
1010
|
+
result_agg[handler.name] = grouped[pos]
|
|
1011
|
+
return result_agg
|
|
920
1012
|
for handler in handlers:
|
|
921
1013
|
handler_result = handler.function(sample_id, preprocess_state)
|
|
922
1014
|
handler_name = handler.name
|
|
923
1015
|
result_agg[handler_name] = handler_result
|
|
924
1016
|
return result_agg
|
|
925
1017
|
|
|
1018
|
+
def get_samples(self, state: DataStateEnum, group_ids: List[Union[int, str]]) -> DatasetSample:
|
|
1019
|
+
"""Group-aware fetch: hand the whole group to each encoder in a single call and return a
|
|
1020
|
+
grouped DatasetSample (inputs/gt as per-sample lists of length B, metadata as per-key lists
|
|
1021
|
+
of length B, index = the group). Grouped results are never stacked here — batching the
|
|
1022
|
+
samples into the model's B is the engine's job. This is the path a group-aware engine calls
|
|
1023
|
+
to read a file once per group. Row order matches group_ids exactly."""
|
|
1024
|
+
self.exec_script()
|
|
1025
|
+
preprocess_result = self._preprocess_result()
|
|
1026
|
+
if state == DataStateEnum.unlabeled and any(
|
|
1027
|
+
sid not in preprocess_result[state].flat_sample_ids for sid in group_ids):
|
|
1028
|
+
# Mirrors get_sample's refresh: the unlabeled preprocess can grow between calls, so a
|
|
1029
|
+
# group_id absent from the current snapshot may just not have been generated yet.
|
|
1030
|
+
self._preprocess_result(update_unlabeled_preprocess=True)
|
|
1031
|
+
preprocess_state = preprocess_result[state]
|
|
1032
|
+
assert preprocess_state.is_grouped, (
|
|
1033
|
+
"get_samples is the group-aware fetch path and requires a grouped preprocess "
|
|
1034
|
+
"response; call get_sample for a flat (non-grouped) dataset.")
|
|
1035
|
+
# All requested ids must belong to a single group (one file). A cross-group request
|
|
1036
|
+
# would force the encoder to load multiple files, defeating the one-load-per-group
|
|
1037
|
+
# contract; partitioning a scattered request by group is the engine's responsibility.
|
|
1038
|
+
distinct_groups = {id(self._locate_group(preprocess_state, sid)[0]) for sid in group_ids}
|
|
1039
|
+
assert len(distinct_groups) == 1, (
|
|
1040
|
+
f"get_samples received sample ids spanning {len(distinct_groups)} groups; a request "
|
|
1041
|
+
f"must be confined to a single group. The engine partitions cross-group requests and "
|
|
1042
|
+
f"issues one get_samples call per group.")
|
|
1043
|
+
inputs = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
1044
|
+
for handler in global_leap_binder.setup_container.inputs}
|
|
1045
|
+
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
1046
|
+
if autoregressive_handler is not None:
|
|
1047
|
+
# AR integrations have no input encoders — the hook's first call supplies each
|
|
1048
|
+
# chain's step-0 model inputs. The hook is strictly per-scalar-chain (never handed
|
|
1049
|
+
# a group), so it is called once per flat id in the group and the per-sample results
|
|
1050
|
+
# are collected into per-key lists of length B, matching the grouped (never-stacked)
|
|
1051
|
+
# contract used for every other encoder here.
|
|
1052
|
+
step_zero_per_sample = []
|
|
1053
|
+
for sid in group_ids:
|
|
1054
|
+
step_zero_result = autoregressive_handler.function(sid, None, None, None,
|
|
1055
|
+
preprocess_state)
|
|
1056
|
+
if not isinstance(step_zero_result, tuple) or len(step_zero_result) != 2 or \
|
|
1057
|
+
not isinstance(step_zero_result[0], dict):
|
|
1058
|
+
raise Exception(
|
|
1059
|
+
'The autoregressive step hook must return a (next_inputs, state) tuple '
|
|
1060
|
+
f'with a dict of initial model inputs on its first call, got '
|
|
1061
|
+
f'{type(step_zero_result)} for sample {sid}.')
|
|
1062
|
+
self._validate_autoregressive_step_inputs(step_zero_result[0], sid)
|
|
1063
|
+
step_zero_per_sample.append(step_zero_result[0])
|
|
1064
|
+
if step_zero_per_sample:
|
|
1065
|
+
for key in self._grouped_dict_keys(step_zero_per_sample, autoregressive_handler.name):
|
|
1066
|
+
inputs[key] = [row[key] for row in step_zero_per_sample]
|
|
1067
|
+
gt = None
|
|
1068
|
+
if state != DataStateEnum.unlabeled:
|
|
1069
|
+
gt = {handler.name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
|
|
1070
|
+
for handler in global_leap_binder.setup_container.ground_truths}
|
|
1071
|
+
|
|
1072
|
+
metadata: Dict[str, Any] = {}
|
|
1073
|
+
metadata_is_none: Dict[str, Any] = {}
|
|
1074
|
+
for handler in global_leap_binder.setup_container.metadata:
|
|
1075
|
+
rows = handler.function(group_ids, preprocess_state) # length-B sequence of scalars/dicts
|
|
1076
|
+
if len(rows) > 0 and isinstance(rows[0], dict):
|
|
1077
|
+
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1078
|
+
name = "{}_{}".format(handler.name, key)
|
|
1079
|
+
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
1080
|
+
metadata[name] = [v for v, _ in converted]
|
|
1081
|
+
metadata_is_none[name] = [is_none for _, is_none in converted]
|
|
1082
|
+
else:
|
|
1083
|
+
converted = [self._convert_metadata_to_correct_type(handler.name, row) for row in rows]
|
|
1084
|
+
metadata[handler.name] = [v for v, _ in converted]
|
|
1085
|
+
metadata_is_none[handler.name] = [is_none for _, is_none in converted]
|
|
1086
|
+
|
|
1087
|
+
# custom_latent_space is group-aware like the input/GT encoders: hand it the whole group in a
|
|
1088
|
+
# single call so a file-backed latent fn loads the file once, and normalize to (B, d). This
|
|
1089
|
+
# keeps the mandatory group path non-lossy. instance_masks stay None here: they are a
|
|
1090
|
+
# per-(sample, instance) concern that needs an instance_id the group fetch does not carry.
|
|
1091
|
+
custom_latent_space = None
|
|
1092
|
+
if global_leap_binder.setup_container.custom_latent_space is not None:
|
|
1093
|
+
latent_fn = global_leap_binder.setup_container.custom_latent_space.function
|
|
1094
|
+
custom_latent_space = self._to_grouped_list(latent_fn(group_ids, preprocess_state))
|
|
1095
|
+
|
|
1096
|
+
return DatasetSample(inputs=inputs, gt=gt, metadata=metadata, metadata_is_none=metadata_is_none,
|
|
1097
|
+
index=list(group_ids), state=state, custom_latent_space=custom_latent_space,
|
|
1098
|
+
instance_masks=None)
|
|
1099
|
+
|
|
926
1100
|
def _get_inputs(self, state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
|
|
927
1101
|
inputs = self._get_dataset_handlers(global_leap_binder.setup_container.inputs, state, sample_id)
|
|
928
1102
|
autoregressive_handler = global_leap_binder.setup_container.autoregressive_step
|
|
@@ -1026,12 +1200,18 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1026
1200
|
sample_id = original_local_id
|
|
1027
1201
|
else:
|
|
1028
1202
|
preprocess_state = preprocess_result[state]
|
|
1203
|
+
group_ids, pos = (None, None)
|
|
1204
|
+
if preprocess_state.is_grouped:
|
|
1205
|
+
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
1029
1206
|
for handler in global_leap_binder.setup_container.metadata:
|
|
1030
1207
|
if requested_metadata_names:
|
|
1031
1208
|
if not is_metadata_name_starts_with_handler_name(handler):
|
|
1032
1209
|
continue
|
|
1033
1210
|
|
|
1034
|
-
|
|
1211
|
+
if preprocess_state.is_grouped:
|
|
1212
|
+
handler_result = handler.function(group_ids, preprocess_state)[pos]
|
|
1213
|
+
else:
|
|
1214
|
+
handler_result = handler.function(sample_id, preprocess_state)
|
|
1035
1215
|
if isinstance(handler_result, dict):
|
|
1036
1216
|
for single_metadata_name, single_metadata_result in handler_result.items():
|
|
1037
1217
|
handler_name = f'{handler.name}_{single_metadata_name}'
|
|
@@ -1050,6 +1230,66 @@ class LeapLoader(LeapLoaderBase):
|
|
|
1050
1230
|
|
|
1051
1231
|
return result_agg, is_none
|
|
1052
1232
|
|
|
1233
|
+
def _grouped_metadata_for_group(self, preprocess_state: PreprocessResponse,
|
|
1234
|
+
group_ids: List[Union[int, str]],
|
|
1235
|
+
requested_metadata_names: Optional[List[str]]
|
|
1236
|
+
) -> Tuple[Dict[str, List[Any]], Dict[str, List[bool]]]:
|
|
1237
|
+
def is_wanted(_handler):
|
|
1238
|
+
if not requested_metadata_names:
|
|
1239
|
+
return True
|
|
1240
|
+
for metadata_name in requested_metadata_names:
|
|
1241
|
+
if metadata_name.startswith(_handler.name + '_') or metadata_name == _handler.name:
|
|
1242
|
+
return True
|
|
1243
|
+
return False
|
|
1244
|
+
|
|
1245
|
+
results: Dict[str, List[Any]] = {}
|
|
1246
|
+
is_none: Dict[str, List[bool]] = {}
|
|
1247
|
+
for handler in global_leap_binder.setup_container.metadata:
|
|
1248
|
+
if requested_metadata_names and not is_wanted(handler):
|
|
1249
|
+
continue
|
|
1250
|
+
rows = handler.function(group_ids, preprocess_state)
|
|
1251
|
+
if len(rows) > 0 and isinstance(rows[0], dict):
|
|
1252
|
+
for key in self._grouped_dict_keys(rows, handler.name):
|
|
1253
|
+
name = f'{handler.name}_{key}'
|
|
1254
|
+
if requested_metadata_names and name not in requested_metadata_names:
|
|
1255
|
+
continue
|
|
1256
|
+
converted = [self._convert_metadata_to_correct_type(name, row[key]) for row in rows]
|
|
1257
|
+
results[name] = [v for v, _ in converted]
|
|
1258
|
+
is_none[name] = [n for _, n in converted]
|
|
1259
|
+
else:
|
|
1260
|
+
name = handler.name
|
|
1261
|
+
if requested_metadata_names and name not in requested_metadata_names:
|
|
1262
|
+
continue
|
|
1263
|
+
converted = [self._convert_metadata_to_correct_type(name, row) for row in rows]
|
|
1264
|
+
results[name] = [v for v, _ in converted]
|
|
1265
|
+
is_none[name] = [n for _, n in converted]
|
|
1266
|
+
return results, is_none
|
|
1267
|
+
|
|
1268
|
+
def get_metadata_multiple_samples(self, state: DataStateEnum, sample_ids: Union[List[int], List[str]],
|
|
1269
|
+
requested_metadata_names: Optional[List[str]] = None
|
|
1270
|
+
) -> Tuple[Dict[str, Union[List[str], List[int], List[bool], List[float]]],
|
|
1271
|
+
Dict[str, List[bool]]]:
|
|
1272
|
+
preprocess_state = self._preprocess_result().get(state)
|
|
1273
|
+
if preprocess_state is None or not preprocess_state.is_grouped:
|
|
1274
|
+
return super().get_metadata_multiple_samples(state, sample_ids, requested_metadata_names)
|
|
1275
|
+
|
|
1276
|
+
sample_id_type = self.get_sample_id_type()
|
|
1277
|
+
aggregated_results: Dict[str, List[Any]] = {}
|
|
1278
|
+
aggregated_is_none: Dict[str, List[bool]] = {}
|
|
1279
|
+
group_cache: Dict[int, Tuple[Dict[str, List[Any]], Dict[str, List[bool]]]] = {}
|
|
1280
|
+
for sample_id in sample_ids:
|
|
1281
|
+
sample_id = sample_id_type(sample_id)
|
|
1282
|
+
group_ids, pos = self._locate_group(preprocess_state, sample_id)
|
|
1283
|
+
key = id(group_ids)
|
|
1284
|
+
if key not in group_cache:
|
|
1285
|
+
group_cache[key] = self._grouped_metadata_for_group(
|
|
1286
|
+
preprocess_state, group_ids, requested_metadata_names)
|
|
1287
|
+
group_results, group_is_none = group_cache[key]
|
|
1288
|
+
for name, values in group_results.items():
|
|
1289
|
+
aggregated_results.setdefault(name, []).append(values[pos])
|
|
1290
|
+
aggregated_is_none.setdefault(name, []).append(group_is_none[name][pos])
|
|
1291
|
+
return aggregated_results, aggregated_is_none
|
|
1292
|
+
|
|
1053
1293
|
@lru_cache()
|
|
1054
1294
|
def get_sample_id_type(self) -> Type:
|
|
1055
1295
|
preprocess_results = list(self._preprocess_result().values())
|
code_loader/utils.py
CHANGED
|
@@ -17,6 +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
|
+
# 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]
|
|
20
30
|
numpy_result: npt.NDArray[np.float32] = np.array(result)
|
|
21
31
|
return numpy_result
|
|
22
32
|
|
|
@@ -1,7 +1,7 @@
|
|
|
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=
|
|
4
|
+
code_loader/contract/datasetclasses.py,sha256=qJoC2pPrVLwJiNGQlPUmnM4vgGNxEv4LHPjovVunTGc,16741
|
|
5
5
|
code_loader/contract/enums.py,sha256=2q-IV_5g9lLE306DIbWA1c0tn5IhDtxsKxyV1x_Lreg,1671
|
|
6
6
|
code_loader/contract/exceptions.py,sha256=jWqu5i7t-0IG0jGRsKF4DjJdrsdpJjIYpUkN1F4RiyQ,51
|
|
7
7
|
code_loader/contract/mapping.py,sha256=sWJhpng-IkOzQnWQdMT5w2ZZ3X1Z_OOzSwCLXIS7oxE,1446
|
|
@@ -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=-t1N-kFGednAjbWaIe6QtgqwI-EkYn-r868mC2_Kz8A,185311
|
|
26
|
+
code_loader/leaploader.py,sha256=nOBXHRb8NHGL5YQIr4xu-a_2uxCR68ptJPO8s-cXGmU,83738
|
|
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
34
|
code_loader/visualizers/default_visualizers.py,sha256=onRnLE_TXfgLN4o52hQIOOhUcFexGlqJ3xSpQDVLuZM,2604
|
|
35
|
-
code_loader-1.0.
|
|
36
|
-
code_loader-1.0.
|
|
37
|
-
code_loader-1.0.
|
|
38
|
-
code_loader-1.0.
|
|
35
|
+
code_loader-1.0.196.dist-info/LICENSE,sha256=qIwWjdspQeSMTtnFZBC8MuT-95L02FPvzRUdWFxrwJY,1067
|
|
36
|
+
code_loader-1.0.196.dist-info/METADATA,sha256=4lBr3zHokWAXKIg6Re-ijpT97AvOSLhIJS4mZnDPfSc,1090
|
|
37
|
+
code_loader-1.0.196.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
38
|
+
code_loader-1.0.196.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|