code-loader 1.0.214.dev1__tar.gz → 1.0.215__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/PKG-INFO +1 -1
  2. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/contract/datasetclasses.py +37 -12
  3. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/inner_leap_binder/leapbinder.py +58 -2
  4. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/inner_leap_binder/leapbinder_decorators.py +54 -5
  5. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/leaploader.py +40 -3
  6. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/leaploaderbase.py +4 -0
  7. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/utils.py +3 -3
  8. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/pyproject.toml +5 -1
  9. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/LICENSE +0 -0
  10. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/README.md +0 -0
  11. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/__init__.py +0 -0
  12. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/contract/__init__.py +0 -0
  13. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/contract/enums.py +0 -0
  14. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/contract/exceptions.py +0 -0
  15. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/contract/mapping.py +0 -0
  16. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/contract/responsedataclasses.py +0 -0
  17. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/contract/sim_config.py +0 -0
  18. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/contract/visualizer_classes.py +0 -0
  19. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/default_losses.py +0 -0
  20. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/default_metrics.py +0 -0
  21. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/__init__.py +0 -0
  22. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/api.py +0 -0
  23. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/cli_config_utils.py +0 -0
  24. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/client.py +0 -0
  25. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/epoch.py +0 -0
  26. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/experiment.py +0 -0
  27. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/experiment_context.py +0 -0
  28. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/types.py +0 -0
  29. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/utils.py +0 -0
  30. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  31. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/inner_leap_binder/__init__.py +0 -0
  32. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/mixpanel_tracker.py +0 -0
  33. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/plot_functions/__init__.py +0 -0
  34. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/plot_functions/plot_functions.py +0 -0
  35. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/plot_functions/visualize.py +0 -0
  36. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/visualizers/__init__.py +0 -0
  37. {code_loader-1.0.214.dev1 → code_loader-1.0.215}/code_loader/visualizers/default_visualizers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: code-loader
3
- Version: 1.0.214.dev1
3
+ Version: 1.0.215
4
4
  Summary:
5
5
  Home-page: https://github.com/tensorleap/code-loader
6
6
  License: MIT
@@ -25,6 +25,17 @@ _simulation_context: Dict[str, bool] = {"active": False}
25
25
 
26
26
  SampleId = Union[int, str]
27
27
 
28
+ SUPPORTED_SAMPLE_IDS_CONTAINERS = (list, tuple, range)
29
+ SUPPORTED_SAMPLE_IDS = ("a list of str, a list of int (with sample_id_type=int), "
30
+ "or a list of non-empty lists of str or int for grouped samples "
31
+ "(a tuple or range can be used instead of the outer list)")
32
+
33
+ _SAMPLE_ID_TYPES: Dict[type, Tuple[type, ...]] = {str: (str, np.str_), int: (int,)}
34
+
35
+
36
+ def is_sample_id_of_type(sample_id: Any, sample_id_type: type) -> bool:
37
+ return type(sample_id) in _SAMPLE_ID_TYPES.get(sample_id_type, ())
38
+
28
39
 
29
40
  @dataclass
30
41
  class PreprocessResponse:
@@ -69,19 +80,25 @@ class PreprocessResponse:
69
80
  self._grouped = False
70
81
  elif self.sample_ids is not None:
71
82
  given_length = self.length
72
- self._grouped = len(self.sample_ids) > 0 and isinstance(self.sample_ids[0], list)
83
+ assert type(self.sample_ids) in SUPPORTED_SAMPLE_IDS_CONTAINERS, \
84
+ (f"PreprocessResponse.sample_ids should be a list. Got: {type(self.sample_ids).__name__}. "
85
+ f"Supported: {SUPPORTED_SAMPLE_IDS}. Convert it with .tolist() or list(...), "
86
+ f"e.g. sample_ids=df.index.tolist().")
87
+ self._grouped = len(self.sample_ids) > 0 and isinstance(self.sample_ids[0], (list, tuple))
73
88
  if self._grouped:
74
89
  groups = cast(List[List[SampleId]], self.sample_ids)
75
90
  assert len(groups) >= 1, "Grouped PreprocessResponse must have at least one group."
76
91
  for group in groups:
77
- assert isinstance(group, list) and len(group) > 0, \
78
- "Each group in a grouped PreprocessResponse must be a non-empty list."
79
- self.sample_id_type = type(groups[0][0])
80
- assert issubclass(self.sample_id_type, (str, int)), \
81
- f"Sample id should be of type str or int. Got: {self.sample_id_type.__name__}"
92
+ assert type(group) is list and len(group) > 0, \
93
+ (f"Each group in a grouped PreprocessResponse must be a non-empty list. Got: {type(group).__name__}. "
94
+ f"Convert each group with list(...).")
95
+ first_type = type(groups[0][0])
96
+ assert first_type in (str, np.str_, int), \
97
+ f"Sample id should be of type str or int. Got: {first_type.__name__}"
98
+ self.sample_id_type = int if first_type is int else str
82
99
  for group in groups:
83
100
  for sample_id in group:
84
- assert isinstance(sample_id, self.sample_id_type), \
101
+ assert is_sample_id_of_type(sample_id, self.sample_id_type), \
85
102
  f"Sample id should be of type {self.sample_id_type.__name__}. Got: {type(sample_id)}"
86
103
  self.length = sum(len(group) for group in groups)
87
104
  else:
@@ -89,9 +106,13 @@ class PreprocessResponse:
89
106
  self.length = len(flat_ids)
90
107
  if self.sample_id_type is None:
91
108
  self.sample_id_type = str
92
- if self.sample_id_type == str:
93
- for sample_id in flat_ids:
94
- assert isinstance(sample_id, str), f"Sample id should be of type str. Got: {type(sample_id)}"
109
+ assert self.sample_id_type in (str, int), \
110
+ f"PreprocessResponse.sample_id_type should be str or int. Got: {self.sample_id_type}"
111
+ for sample_id in flat_ids:
112
+ assert is_sample_id_of_type(sample_id, self.sample_id_type), \
113
+ (f"Sample id should be of type {self.sample_id_type.__name__}. Got: {type(sample_id)}. "
114
+ f"Supported: {SUPPORTED_SAMPLE_IDS}. Convert NumPy values with .tolist(), "
115
+ f"or pass sample_id_type=int for int ids.")
95
116
  # dataclasses.replace() re-invokes __init__ with both fields already populated
96
117
  # (length previously derived from sample_ids); only reject a genuine mismatch.
97
118
  if given_length is not None:
@@ -155,6 +176,9 @@ class ElementInstance:
155
176
  # masks funnel (utils.to_numpy_return_masks_wrapper) so the engine copies flags rather than
156
177
  # deriving them, exactly as it does for sample-level metadata.
157
178
  instance_metadata_is_none: Optional[Dict[str, bool]] = None
179
+ # Per-instance metric values keyed by the registered name of a tensorleap_custom_instances_metric.
180
+ # Only meaningful on false-positive instances (they have no ground-truth row for the metric to run on).
181
+ instance_metrics: Optional[Dict[str, float]] = None
158
182
 
159
183
  SectionCallableInterface = Callable[[Union[int, str], PreprocessResponse], npt.NDArray[np.float32]]
160
184
  InstanceCallableInterface = Callable[[Union[int, str], PreprocessResponse, int], Optional[ElementInstance]]
@@ -310,7 +334,7 @@ class ElementInstanceMasksHandler:
310
334
  @dataclass
311
335
  class FalsePositiveInstancesHandler:
312
336
  name: str
313
- function: Callable
337
+ function: Callable[..., Any]
314
338
 
315
339
  @dataclass
316
340
  class GroundTruthHandler(DatasetBaseHandler):
@@ -411,7 +435,8 @@ class DatasetIntegrationSetup:
411
435
  inputs: List[InputHandler] = field(default_factory=list)
412
436
  instance_masks: List[ElementInstanceMasksHandler] = field(default_factory=list)
413
437
  false_positive_instances: Optional[FalsePositiveInstancesHandler] = None
414
- false_positive_empty_gt: Dict[str, Callable] = field(default_factory=dict)
438
+ false_positive_empty_gt: Dict[str, Callable[..., Any]] = field(default_factory=dict)
439
+ false_positive_custom_latent_spaces: Dict[str, Callable[..., Any]] = field(default_factory=dict)
415
440
  ground_truths: List[GroundTruthHandler] = field(default_factory=list)
416
441
  metadata: List[MetadataHandler] = field(default_factory=list)
417
442
  prediction_types: List[PredictionTypeHandler] = field(default_factory=list)
@@ -341,7 +341,7 @@ class LeapBinder:
341
341
  self.setup_container.instance_masks.append(ElementInstanceMasksHandler(name, function))
342
342
 
343
343
 
344
- def set_false_positive_instances(self, function: Callable, name: str) -> None:
344
+ def set_false_positive_instances(self, function: Callable[..., Any], name: str) -> None:
345
345
  """
346
346
  Set the false-positive instances handler. It receives (sample_id, preprocess, predictions)
347
347
  and returns a list of ElementInstance, one per false-positive prediction.
@@ -351,7 +351,7 @@ class LeapBinder:
351
351
  function = to_numpy_return_fp_masks_wrapper(function)
352
352
  self.setup_container.false_positive_instances = FalsePositiveInstancesHandler(name, function)
353
353
 
354
- def set_false_positive_empty_gt(self, function: Callable, gt_name: str) -> None:
354
+ def set_false_positive_empty_gt(self, function: Callable[..., Any], gt_name: str) -> None:
355
355
  """
356
356
  Set the empty ground truth for false-positive instance rows of the given GT encoder.
357
357
  It receives (sample_id, preprocess) and returns an array shaped like the GT encoder's output.
@@ -392,6 +392,61 @@ class LeapBinder:
392
392
  f"tensorleap_false_positive_empty_gt for '{name}' returned dtype {empty.dtype}, "
393
393
  f"which cannot be cast to float32.")
394
394
 
395
+ def set_false_positive_custom_latent_space(self, function: Callable[..., Any], name: str) -> None:
396
+ """
397
+ Set the custom latent space hook for false-positive instance rows. It receives
398
+ (sample_id, preprocess, fp_instance) and returns a 1-D vector for the instance-aware
399
+ custom latent space registered under `name`.
400
+ """
401
+ if name in self.setup_container.false_positive_custom_latent_spaces:
402
+ raise Exception(f"tensorleap_false_positive_custom_latent_space for '{name}' is already defined")
403
+ self.setup_container.false_positive_custom_latent_spaces[name] = function
404
+
405
+ def check_false_positive_custom_latent_spaces(
406
+ self, preprocess_result: Optional[Dict[DataStateEnum, PreprocessResponse]] = None) -> None:
407
+ """Every FP latent-space hook must name an instance-aware custom latent space and, when a
408
+ preprocess result is given, return the same shape as it (no-op without an FP encoder)."""
409
+ if self.setup_container.false_positive_instances is None:
410
+ return
411
+ for name in self.setup_container.false_positive_custom_latent_spaces:
412
+ handler = self.setup_container.custom_latent_spaces.get(name)
413
+ if handler is None or not handler.instance_aware:
414
+ raise Exception(
415
+ f"tensorleap_false_positive_custom_latent_space validation failed: '{name}' is not an "
416
+ f"instance custom latent space (tensorleap_instance_custom_latent_space)")
417
+ if preprocess_result is not None:
418
+ self._check_false_positive_custom_latent_space_shapes(preprocess_result)
419
+
420
+ def _check_false_positive_custom_latent_space_shapes(
421
+ self, preprocess_result: Dict[DataStateEnum, PreprocessResponse]) -> None:
422
+ """Call each FP latent-space hook with a real GT ElementInstance and compare its shape with
423
+ the instance custom latent space's. FP and GT rows of one latent space are usually stored in
424
+ different bulks, so a width mismatch would otherwise be dropped silently at load time.
425
+ Skipped when there is no GT instance to probe with (no mask encoder, or no training sample
426
+ with an instance)."""
427
+ fp_hooks = self.setup_container.false_positive_custom_latent_spaces
428
+ if not fp_hooks or not self.setup_container.instance_masks:
429
+ return
430
+ training = preprocess_result.get(DataStateEnum.training)
431
+ if training is None or not training.sample_ids_to_instance_mappings:
432
+ return
433
+ sample_id = next((sid for sid, instance_ids in training.sample_ids_to_instance_mappings.items()
434
+ if instance_ids), None)
435
+ if sample_id is None:
436
+ return
437
+ gt_instance = self.setup_container.instance_masks[0].function(sample_id, training, 0)
438
+ if gt_instance is None:
439
+ return
440
+ for name, fp_hook in fp_hooks.items():
441
+ instance_ls = cast(InstanceSectionCallableInterface,
442
+ self.setup_container.custom_latent_spaces[name].function)
443
+ gt_shape = np.asarray(instance_ls(sample_id, training, 0)).shape
444
+ fp_shape = np.asarray(fp_hook(sample_id, training, gt_instance)).shape
445
+ if fp_shape != gt_shape:
446
+ raise Exception(
447
+ f"tensorleap_false_positive_custom_latent_space validation failed: '{name}' returns shape "
448
+ f"{fp_shape} but the instance custom latent space returns {gt_shape}")
449
+
395
450
  def add_custom_loss(self, function: CustomCallableInterface, name: str) -> None:
396
451
  """
397
452
  Add a custom loss function to the setup.
@@ -1075,6 +1130,7 @@ class LeapBinder:
1075
1130
  self.check_preprocess(preprocess_result)
1076
1131
  self.check_handlers(preprocess_result)
1077
1132
  self.check_false_positive_empty_gt(preprocess_result)
1133
+ self.check_false_positive_custom_latent_spaces(preprocess_result)
1078
1134
  self.check_simulations()
1079
1135
  self.validate_ignore_latent_spaces()
1080
1136
  print("Successful!")
@@ -27,6 +27,7 @@ from code_loader.contract.datasetclasses import CustomCallableInterfaceMultiArgs
27
27
  CustomMultipleReturnCallableInterfaceMultiArgs, ConfusionMatrixCallableInterfaceMultiArgs, CustomCallableInterface, \
28
28
  VisualizerCallableInterface, MetadataSectionCallableInterface, PreprocessResponse, SectionCallableInterface, \
29
29
  ConfusionMatrixElement, SamplePreprocessResponse, PredictionTypeHandler, InstanceCallableInterface, ElementInstance, \
30
+ is_sample_id_of_type, \
30
31
  InstanceLengthCallableInterface, InstanceSectionCallableInterface, AutoregressiveStepCallableInterface, \
31
32
  MAX_CUSTOM_LATENT_SPACE_DIM, CUSTOM_LATENT_SPACE_WARN_DIM
32
33
  from code_loader.contract.enums import MetricDirection, LeapDataType, DatasetMetadataType, DataStateType, \
@@ -236,7 +237,7 @@ def _validate_id_or_group(sample_id, preprocess_response, func_name):
236
237
  f'is grouped. Pass a group (list of sample ids) for a grouped dataset.')
237
238
  ids = sample_id if is_group else [sample_id]
238
239
  for sid in ids:
239
- assert type(sid) == preprocess_response.sample_id_type, \
240
+ assert is_sample_id_of_type(sid, preprocess_response.sample_id_type), \
240
241
  (f'{func_name}() validation failed: '
241
242
  f'Argument sample_id should be as the same type as defined in the preprocess response '
242
243
  f'{preprocess_response.sample_id_type}. Got {type(sid)}.')
@@ -2491,7 +2492,7 @@ def tensorleap_autoregressive_step(latent_space_aggregation: str = 'last_step'):
2491
2492
  (f'{user_function.__name__}() validation failed: '
2492
2493
  f'the first call (prev_inputs=None, prev_outputs=None) must pass state=None. '
2493
2494
  f'Got state of type {type(state).__name__}.')
2494
- assert type(sample_id) == preprocess_response.sample_id_type, \
2495
+ assert is_sample_id_of_type(sample_id, preprocess_response.sample_id_type), \
2495
2496
  (f'{user_function.__name__}() validation failed: '
2496
2497
  f'Argument sample_id should be as the same type as defined in the preprocess response '
2497
2498
  f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
@@ -3360,7 +3361,7 @@ def tensorleap_element_instance_preprocess(
3360
3361
  "Remove the instance encoders or don't group this dataset.")
3361
3362
  sample_ids_to_instance_mappings = {}
3362
3363
  instance_to_sample_ids_mappings = {}
3363
- all_sample_ids = preprocess_response.sample_ids.copy()
3364
+ all_sample_ids = list(preprocess_response.sample_ids)
3364
3365
  for sample_id in preprocess_response.sample_ids:
3365
3366
  instances_length = instance_length_encoder(sample_id, preprocess_response)
3366
3367
  instances_ids = [f'{sample_id}_{instance_id}' for instance_id in range(instances_length)]
@@ -3458,7 +3459,7 @@ def tensorleap_instances_masks_encoder(name: str):
3458
3459
  assert isinstance(preprocess_response, PreprocessResponse), \
3459
3460
  (f'tensorleap_instances_masks_encoder validation failed: '
3460
3461
  f'Argument preprocess_response should be a PreprocessResponse. Got {type(preprocess_response)}.')
3461
- assert type(sample_id) == preprocess_response.sample_id_type, \
3462
+ assert is_sample_id_of_type(sample_id, preprocess_response.sample_id_type), \
3462
3463
  (f'tensorleap_instances_masks_encoder validation failed: '
3463
3464
  f'Argument sample_id should be as the same type as defined in the preprocess response '
3464
3465
  f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
@@ -3603,6 +3604,54 @@ def tensorleap_false_positive_empty_gt(gt_name: str):
3603
3604
  return decorating_function
3604
3605
 
3605
3606
 
3607
+ def tensorleap_false_positive_custom_latent_space(name: str):
3608
+ """Compute the instance custom latent space `name` (a tensorleap_instance_custom_latent_space)
3609
+ for a false-positive instance. Signature: fn(sample_id, preprocess, fp_instance) -> 1-D np.ndarray."""
3610
+ def decorating_function(user_function: Callable):
3611
+ def _validate_input_args(sample_id, preprocess_response, fp_instance):
3612
+ assert isinstance(sample_id, (str, int)), \
3613
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3614
+ f'Argument sample_id should be str or int. Got {type(sample_id)}.')
3615
+ assert isinstance(preprocess_response, PreprocessResponse), \
3616
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3617
+ f'Argument preprocess_response should be a PreprocessResponse. Got {type(preprocess_response)}.')
3618
+ assert isinstance(fp_instance, ElementInstance), \
3619
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3620
+ f'Argument fp_instance should be an ElementInstance. Got {type(fp_instance)}.')
3621
+
3622
+ def _validate_result(result):
3623
+ assert isinstance(result, np.ndarray), \
3624
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3625
+ f'The return type should be a numpy array. Got {type(result)}.')
3626
+ assert result.ndim == 1, \
3627
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3628
+ f'The returned array should be 1-D. Got shape {result.shape}.')
3629
+
3630
+ def inner_without_validate(sample_id, preprocess_response, fp_instance):
3631
+ global _called_from_inside_tl_decorator
3632
+ _called_from_inside_tl_decorator += 1
3633
+ try:
3634
+ result = user_function(sample_id, preprocess_response, fp_instance)
3635
+ finally:
3636
+ _called_from_inside_tl_decorator -= 1
3637
+ return result
3638
+
3639
+ leap_binder.set_false_positive_custom_latent_space(inner_without_validate, name)
3640
+
3641
+ def inner(sample_id, preprocess_response, fp_instance):
3642
+ if os.environ.get(mapping_runtime_mode_env_var_mame):
3643
+ return np.zeros(0, dtype=np.float32)
3644
+
3645
+ _validate_input_args(sample_id, preprocess_response, fp_instance)
3646
+ result = inner_without_validate(sample_id, preprocess_response, fp_instance)
3647
+ _validate_result(result)
3648
+ return result
3649
+
3650
+ return inner
3651
+
3652
+ return decorating_function
3653
+
3654
+
3606
3655
  def tensorleap_instances_length_encoder(name: str):
3607
3656
  def decorating_function(user_function: InstanceLengthCallableInterface):
3608
3657
  def _validate_input_args(sample_id: str, preprocess_response: PreprocessResponse):
@@ -3612,7 +3661,7 @@ def tensorleap_instances_length_encoder(name: str):
3612
3661
  assert isinstance(preprocess_response, PreprocessResponse), \
3613
3662
  (f'tensorleap_instances_length_encoder validation failed: '
3614
3663
  f'Argument preprocess_response should be a PreprocessResponse. Got {type(preprocess_response)}.')
3615
- assert type(sample_id) == preprocess_response.sample_id_type, \
3664
+ assert is_sample_id_of_type(sample_id, preprocess_response.sample_id_type), \
3616
3665
  (f'tensorleap_instances_length_encoder validation failed: '
3617
3666
  f'Argument sample_id should be as the same type as defined in the preprocess response '
3618
3667
  f'{preprocess_response.sample_id_type}. Got {type(sample_id)}.')
@@ -653,7 +653,9 @@ class LeapLoader(LeapLoaderBase):
653
653
  return None
654
654
  test_result = DatasetTestResultPayload(handler.name)
655
655
  try:
656
- global_leap_binder.check_false_positive_empty_gt(self._preprocess_result())
656
+ preprocess_result = self._preprocess_result()
657
+ global_leap_binder.check_false_positive_empty_gt(preprocess_result)
658
+ global_leap_binder.check_false_positive_custom_latent_spaces(preprocess_result)
657
659
  except Exception as e:
658
660
  line_number, file_name, stacktrace = get_root_exception_file_and_line_number()
659
661
  error_string = f"{repr(e)} in file {file_name}, line_number: {line_number}\nStacktrace:\n{stacktrace}"
@@ -1825,7 +1827,7 @@ class LeapLoader(LeapLoaderBase):
1825
1827
  row = {name: array[i] for name, array in predictions.items()}
1826
1828
  try:
1827
1829
  instances = handler.function(self._native_sample_id(sample_id), preprocess_response, row)
1828
- self._validate_false_positive_instances(instances)
1830
+ self._validate_false_positive_instances(instances, set(self._instance_metric_handler_by_name()))
1829
1831
  except Exception as e:
1830
1832
  print(f"tensorleap_false_positive_instances_encoder failed for sample {sample_id!r} "
1831
1833
  f"(state {state.name}): {e!r}")
@@ -1835,7 +1837,7 @@ class LeapLoader(LeapLoaderBase):
1835
1837
  return results
1836
1838
 
1837
1839
  @staticmethod
1838
- def _validate_false_positive_instances(instances: Any) -> None:
1840
+ def _validate_false_positive_instances(instances: Any, registered_instance_metrics: Set[str]) -> None:
1839
1841
  prefix = "tensorleap_false_positive_instances_encoder validation failed: "
1840
1842
  if not isinstance(instances, list):
1841
1843
  raise Exception(f"{prefix}The return type should be a list of ElementInstance. "
@@ -1844,6 +1846,21 @@ class LeapLoader(LeapLoaderBase):
1844
1846
  if not isinstance(instance, ElementInstance):
1845
1847
  raise Exception(f"{prefix}Element {j} of the returned list should be an ElementInstance. "
1846
1848
  f"Got {type(instance)}.")
1849
+ if instance.instance_metrics is None:
1850
+ continue
1851
+ if not isinstance(instance.instance_metrics, dict):
1852
+ raise Exception(f"{prefix}instance_metrics of element {j} should be a dict. "
1853
+ f"Got {type(instance.instance_metrics)}.")
1854
+ for key, value in instance.instance_metrics.items():
1855
+ if key not in registered_instance_metrics:
1856
+ raise Exception(f"{prefix}instance_metrics key {key!r} of element {j} is not the name of a "
1857
+ f"registered tensorleap_custom_instances_metric. "
1858
+ f"Registered: {sorted(registered_instance_metrics)}.")
1859
+ try:
1860
+ instance.instance_metrics[key] = float(value)
1861
+ except (TypeError, ValueError):
1862
+ raise Exception(f"{prefix}instance_metrics[{key!r}] of element {j} should be castable to "
1863
+ f"float. Got {type(value)}.")
1847
1864
 
1848
1865
  def get_false_positive_empty_gt(self, state: DataStateEnum,
1849
1866
  sample_id: Union[str, int]) -> Dict[str, npt.NDArray[np.float32]]:
@@ -1859,6 +1876,26 @@ class LeapLoader(LeapLoaderBase):
1859
1876
  empty_gt[gt_name] = value.astype(np.float32, copy=False)
1860
1877
  return empty_gt
1861
1878
 
1879
+ def get_false_positive_custom_latent_spaces(self, state: DataStateEnum, sample_id: Union[str, int],
1880
+ fp_instance: ElementInstance) -> Dict[str, npt.NDArray[np.float32]]:
1881
+ self.exec_script()
1882
+ hooks = global_leap_binder.setup_container.false_positive_custom_latent_spaces
1883
+ if not hooks:
1884
+ return {}
1885
+ preprocess_response = self._preprocess_result()[state]
1886
+ sample_id = self._native_sample_id(sample_id)
1887
+ vectors: Dict[str, npt.NDArray[np.float32]] = {}
1888
+ for name, function in hooks.items():
1889
+ value = function(sample_id, preprocess_response, fp_instance)
1890
+ if not isinstance(value, np.ndarray):
1891
+ raise Exception(f"tensorleap_false_positive_custom_latent_space validation failed: "
1892
+ f"The return type for '{name}' should be a numpy array. Got {type(value)}.")
1893
+ if value.ndim != 1:
1894
+ raise Exception(f"tensorleap_false_positive_custom_latent_space validation failed: "
1895
+ f"The return value for '{name}' should be a 1-D array. Got shape {value.shape}.")
1896
+ vectors[name] = value.astype(np.float32, copy=False)
1897
+ return vectors
1898
+
1862
1899
  def get_instances_data(self, state: DataStateEnum) -> Tuple[Dict[str, List[str]], Dict[str, str]]:
1863
1900
  preprocess_result = self._preprocess_result()
1864
1901
  preprocess_state = preprocess_result.get(state)
@@ -87,6 +87,10 @@ class LeapLoaderBase:
87
87
  sample_id: Union[str, int]) -> Dict[str, npt.NDArray[np.float32]]:
88
88
  raise NotImplementedError("get_false_positive_empty_gt is not implemented by this loader")
89
89
 
90
+ def get_false_positive_custom_latent_spaces(self, state: DataStateEnum, sample_id: Union[str, int],
91
+ fp_instance: ElementInstance) -> Dict[str, npt.NDArray[np.float32]]:
92
+ return {}
93
+
90
94
  def get_metadata_multiple_samples(self, state: DataStateEnum, sample_ids: Union[List[int], List[str]],
91
95
  requested_metadata_names: Optional[List[str]] = None
92
96
  ) -> Tuple[Dict[str, Union[List[str], List[int], List[bool],
@@ -6,7 +6,7 @@ import pickle
6
6
  import sys
7
7
  from pathlib import Path
8
8
  from types import TracebackType
9
- from typing import List, Union, Tuple, Any, Callable, Dict, Optional
9
+ from typing import List, Union, Tuple, Any, Callable, Dict, Optional, cast
10
10
  import traceback
11
11
  import numpy as np
12
12
  import numpy.typing as npt
@@ -53,11 +53,11 @@ def to_numpy_return_masks_wrapper(encoder_function: InstanceCallableInterface) -
53
53
  return numpy_encoder_function
54
54
 
55
55
 
56
- def to_numpy_return_fp_masks_wrapper(encoder_function: Callable) -> Callable:
56
+ def to_numpy_return_fp_masks_wrapper(encoder_function: Callable[..., Any]) -> Callable[..., List[ElementInstance]]:
57
57
  def numpy_encoder_function(idx: Union[int, str], samples: PreprocessResponse, predictions: Dict[str, Any]) -> List[ElementInstance]:
58
58
  result = encoder_function(idx, samples, predictions)
59
59
  if not isinstance(result, list):
60
- return result # malformed: the loader reports it with a clear message
60
+ return cast(List[ElementInstance], result) # malformed: the loader reports it with a clear message
61
61
  for instance in result:
62
62
  if not isinstance(instance, ElementInstance):
63
63
  continue
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "code-loader"
3
- version = "1.0.214.dev1"
3
+ version = "1.0.215"
4
4
  description = ""
5
5
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
6
6
  license = "MIT"
@@ -30,6 +30,10 @@ pytest-watch = "^4.2.0"
30
30
  mypy = "1.3.0"
31
31
  onnxruntime = {version = ">=1.0.0", python = ">=3.10"}
32
32
  onnx = {version = ">=1.0.0", python = ">=3.10"}
33
+ pandas = [
34
+ { version = "^2.0.3", python = ">=3.8,<3.11" },
35
+ { version = "^2.2.3", python = ">=3.11,<3.13" }
36
+ ]
33
37
 
34
38
  [build-system]
35
39
  requires = ["poetry-core>=1.0.0"]