code-loader 1.0.212__tar.gz → 1.0.214__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.212 → code_loader-1.0.214}/PKG-INFO +1 -1
  2. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/contract/datasetclasses.py +11 -0
  3. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/inner_leap_binder/leapbinder.py +116 -3
  4. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/inner_leap_binder/leapbinder_decorators.py +201 -23
  5. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/leaploader.py +128 -0
  6. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/leaploaderbase.py +20 -1
  7. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/utils.py +16 -0
  8. {code_loader-1.0.212 → code_loader-1.0.214}/pyproject.toml +1 -1
  9. {code_loader-1.0.212 → code_loader-1.0.214}/LICENSE +0 -0
  10. {code_loader-1.0.212 → code_loader-1.0.214}/README.md +0 -0
  11. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/__init__.py +0 -0
  12. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/contract/__init__.py +0 -0
  13. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/contract/enums.py +0 -0
  14. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/contract/exceptions.py +0 -0
  15. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/contract/mapping.py +0 -0
  16. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/contract/responsedataclasses.py +0 -0
  17. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/contract/sim_config.py +0 -0
  18. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/contract/visualizer_classes.py +0 -0
  19. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/default_losses.py +0 -0
  20. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/default_metrics.py +0 -0
  21. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/__init__.py +0 -0
  22. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/api.py +0 -0
  23. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/cli_config_utils.py +0 -0
  24. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/client.py +0 -0
  25. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/epoch.py +0 -0
  26. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/experiment.py +0 -0
  27. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/experiment_context.py +0 -0
  28. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/types.py +0 -0
  29. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/utils.py +0 -0
  30. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  31. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/inner_leap_binder/__init__.py +0 -0
  32. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/mixpanel_tracker.py +0 -0
  33. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/plot_functions/__init__.py +0 -0
  34. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/plot_functions/plot_functions.py +0 -0
  35. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/plot_functions/visualize.py +0 -0
  36. {code_loader-1.0.212 → code_loader-1.0.214}/code_loader/visualizers/__init__.py +0 -0
  37. {code_loader-1.0.212 → code_loader-1.0.214}/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.212
3
+ Version: 1.0.214
4
4
  Summary:
5
5
  Home-page: https://github.com/tensorleap/code-loader
6
6
  License: MIT
@@ -155,6 +155,9 @@ class ElementInstance:
155
155
  # masks funnel (utils.to_numpy_return_masks_wrapper) so the engine copies flags rather than
156
156
  # deriving them, exactly as it does for sample-level metadata.
157
157
  instance_metadata_is_none: Optional[Dict[str, bool]] = None
158
+ # Per-instance metric values keyed by the registered name of a tensorleap_custom_instances_metric.
159
+ # Only meaningful on false-positive instances (they have no ground-truth row for the metric to run on).
160
+ instance_metrics: Optional[Dict[str, float]] = None
158
161
 
159
162
  SectionCallableInterface = Callable[[Union[int, str], PreprocessResponse], npt.NDArray[np.float32]]
160
163
  InstanceCallableInterface = Callable[[Union[int, str], PreprocessResponse, int], Optional[ElementInstance]]
@@ -307,6 +310,11 @@ class ElementInstanceMasksHandler:
307
310
  name: str
308
311
  function: InstanceCallableInterface
309
312
 
313
+ @dataclass
314
+ class FalsePositiveInstancesHandler:
315
+ name: str
316
+ function: Callable
317
+
310
318
  @dataclass
311
319
  class GroundTruthHandler(DatasetBaseHandler):
312
320
  shape: Optional[List[int]] = None
@@ -405,6 +413,9 @@ class DatasetIntegrationSetup:
405
413
  visualizers: List[VisualizerHandler] = field(default_factory=list)
406
414
  inputs: List[InputHandler] = field(default_factory=list)
407
415
  instance_masks: List[ElementInstanceMasksHandler] = field(default_factory=list)
416
+ false_positive_instances: Optional[FalsePositiveInstancesHandler] = None
417
+ false_positive_empty_gt: Dict[str, Callable] = field(default_factory=dict)
418
+ false_positive_custom_latent_spaces: Dict[str, Callable] = field(default_factory=dict)
408
419
  ground_truths: List[GroundTruthHandler] = field(default_factory=list)
409
420
  metadata: List[MetadataHandler] = field(default_factory=list)
410
421
  prediction_types: List[PredictionTypeHandler] = field(default_factory=list)
@@ -16,7 +16,7 @@ from code_loader.contract.datasetclasses import SectionCallableInterface, InputH
16
16
  CustomCallableInterfaceMultiArgs, ConfusionMatrixCallableInterfaceMultiArgs, LeapData, \
17
17
  CustomMultipleReturnCallableInterfaceMultiArgs, DatasetBaseHandler, custom_latent_space_attribute, \
18
18
  RawInputsForHeatmap, VisualizerHandlerData, MetricHandlerData, CustomLossHandlerData, SamplePreprocessResponse, \
19
- ElementInstanceMasksHandler, InstanceCallableInterface, InstanceSectionCallableInterface, \
19
+ ElementInstanceMasksHandler, FalsePositiveInstancesHandler, InstanceCallableInterface, InstanceSectionCallableInterface, \
20
20
  CustomLatentSpaceHandler, InstanceMetricHandler, MAX_CUSTOM_LATENT_SPACES, \
21
21
  SimulationHandler, _simulation_context, AutoregressiveStepHandler, AutoregressiveStepCallableInterface, \
22
22
  AUTOREGRESSIVE_LATENT_SPACE_AGGREGATIONS, AUTOREGRESSIVE_IMPLICIT_ARG_NAMES, \
@@ -28,7 +28,8 @@ from code_loader.contract.responsedataclasses import DatasetTestResultPayload, L
28
28
  from code_loader.contract.visualizer_classes import map_leap_data_type_to_visualizer_class
29
29
  from code_loader.default_losses import loss_name_to_function
30
30
  from code_loader.default_metrics import metrics_names_to_functions_and_direction
31
- from code_loader.utils import to_numpy_return_wrapper, get_shape, to_numpy_return_masks_wrapper
31
+ from code_loader.utils import to_numpy_return_wrapper, get_shape, to_numpy_return_masks_wrapper, \
32
+ to_numpy_return_fp_masks_wrapper
32
33
  from code_loader.contract.sim_config import parse_sim_config, sample_sim_params
33
34
  from code_loader.visualizers.default_visualizers import DefaultVisualizer, \
34
35
  default_graph_visualizer, \
@@ -340,6 +341,112 @@ class LeapBinder:
340
341
  self.setup_container.instance_masks.append(ElementInstanceMasksHandler(name, function))
341
342
 
342
343
 
344
+ def set_false_positive_instances(self, function: Callable, name: str) -> None:
345
+ """
346
+ Set the false-positive instances handler. It receives (sample_id, preprocess, predictions)
347
+ and returns a list of ElementInstance, one per false-positive prediction.
348
+ """
349
+ if self.setup_container.false_positive_instances is not None:
350
+ raise Exception("only one tensorleap_false_positive_instances_encoder can be defined")
351
+ function = to_numpy_return_fp_masks_wrapper(function)
352
+ self.setup_container.false_positive_instances = FalsePositiveInstancesHandler(name, function)
353
+
354
+ def set_false_positive_empty_gt(self, function: Callable, gt_name: str) -> None:
355
+ """
356
+ Set the empty ground truth for false-positive instance rows of the given GT encoder.
357
+ It receives (sample_id, preprocess) and returns an array shaped like the GT encoder's output.
358
+ """
359
+ if gt_name in self.setup_container.false_positive_empty_gt:
360
+ raise Exception(f"tensorleap_false_positive_empty_gt for '{gt_name}' is already defined")
361
+ self.setup_container.false_positive_empty_gt[gt_name] = function
362
+
363
+ def check_false_positive_empty_gt(self, preprocess_result: Dict[DataStateEnum, PreprocessResponse]) -> None:
364
+ """Validate the empty-GT functions against the GT encoders (no-op without an FP encoder)."""
365
+ if self.setup_container.false_positive_instances is None:
366
+ return
367
+ gt_handlers = {handler.name: handler for handler in self.setup_container.ground_truths}
368
+ empty_gt = self.setup_container.false_positive_empty_gt
369
+ missing = [name for name in gt_handlers if name not in empty_gt]
370
+ if missing:
371
+ raise Exception(
372
+ f"tensorleap_false_positive_instances_encoder requires a tensorleap_false_positive_empty_gt "
373
+ f"for every ground truth encoder. Missing for: {missing}.")
374
+ unknown = [name for name in empty_gt if name not in gt_handlers]
375
+ if unknown:
376
+ raise Exception(
377
+ f"tensorleap_false_positive_empty_gt names {unknown} do not match any registered ground truth "
378
+ f"encoder. Registered ground truth encoders: {list(gt_handlers)}.")
379
+ training = preprocess_result.get(DataStateEnum.training)
380
+ if training is None or not training.sample_ids or training.is_grouped:
381
+ return
382
+ sample_id = cast(Union[int, str], training.sample_ids[0])
383
+ for name, handler in gt_handlers.items():
384
+ gt = np.asarray(handler.function(sample_id, training))
385
+ empty = np.asarray(empty_gt[name](sample_id, training))
386
+ if empty.shape != gt.shape:
387
+ raise Exception(
388
+ f"tensorleap_false_positive_empty_gt for '{name}' returned shape {empty.shape}, but the "
389
+ f"ground truth encoder returns shape {gt.shape}.")
390
+ if not np.can_cast(empty.dtype, np.float32, casting='same_kind'):
391
+ raise Exception(
392
+ f"tensorleap_false_positive_empty_gt for '{name}' returned dtype {empty.dtype}, "
393
+ f"which cannot be cast to float32.")
394
+
395
+ def set_false_positive_custom_latent_space(self, function: Callable, 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
+
343
450
  def add_custom_loss(self, function: CustomCallableInterface, name: str) -> None:
344
451
  """
345
452
  Add a custom loss function to the setup.
@@ -798,6 +905,10 @@ class LeapBinder:
798
905
  for state, preprocess_response in preprocess_result.items():
799
906
  if preprocess_response.length is None or preprocess_response.length <= 0:
800
907
  raise Exception('Invalid dataset length')
908
+ if preprocess_response.is_grouped and self.setup_container.false_positive_instances is not None:
909
+ raise Exception(
910
+ 'tensorleap_false_positive_instances_encoder is not supported with a grouped '
911
+ f'preprocess response (grouped state: {state.name}). Use flat sample_ids.')
801
912
  if unlabeled_preprocess_handler is not None and state == DataStateEnum.unlabeled:
802
913
  unlabeled_preprocess_handler.data_length = preprocess_response.length
803
914
  else:
@@ -1018,6 +1129,8 @@ class LeapBinder:
1018
1129
  preprocess_result = self.get_preprocess_result()
1019
1130
  self.check_preprocess(preprocess_result)
1020
1131
  self.check_handlers(preprocess_result)
1132
+ self.check_false_positive_empty_gt(preprocess_result)
1133
+ self.check_false_positive_custom_latent_spaces(preprocess_result)
1021
1134
  self.check_simulations()
1022
1135
  self.validate_ignore_latent_spaces()
1023
1136
  print("Successful!")
@@ -1058,7 +1171,7 @@ class LeapBinder:
1058
1171
  "Simulations are not supported together with tensorleap_autoregressive_step yet: "
1059
1172
  "the autoregressive entry points do not resolve synthetic sample ids. Remove the "
1060
1173
  "simulation decorators or the autoregressive hook.")
1061
- if self.setup_container.instance_masks:
1174
+ if self.setup_container.instance_masks or self.setup_container.false_positive_instances is not None:
1062
1175
  raise Exception(
1063
1176
  "Element instances are not supported together with tensorleap_autoregressive_step: "
1064
1177
  "instance generation masks the sample's encoded inputs, which do not exist in an "
@@ -3254,29 +3254,59 @@ def tensorleap_simulation(name: str, sim_params: dict):
3254
3254
  return decorating_function
3255
3255
 
3256
3256
 
3257
+ def _register_builtin_instance_metadata() -> None:
3258
+ """Idempotently register the builtin instance metadata handler.
3259
+
3260
+ Shared by the element-instance preprocess and the false-positive encoder, since either alone
3261
+ makes the engine produce instance samples.
3262
+ """
3263
+ if any(h.name == "builtin_instance_metadata" for h in leap_binder.setup_container.metadata):
3264
+ return
3265
+
3266
+ def builtin_instance_metadata(idx: str, preprocess: PreprocessResponse) -> Dict[str, str]:
3267
+ result = {'is_instance': '0', 'original_sample_id': idx, 'instance_name': 'none'}
3268
+ if leap_binder.setup_container.false_positive_instances is not None:
3269
+ result['is_false_positive'] = '0'
3270
+ return result
3271
+
3272
+ leap_binder.set_metadata(builtin_instance_metadata, "builtin_instance_metadata")
3273
+
3274
+
3275
+ def _register_builtin_instance_extra_metadata(metadata_types: Dict[str, DatasetMetadataType]) -> None:
3276
+ """Register (or extend) the `builtin_instance_extra_metadata` column declaration.
3277
+
3278
+ Shared by the element-instance preprocess (keys probed from the GT mask encoder) and the
3279
+ false-positive encoder (keys declared with `instance_metadata_types`). Idempotent and merging:
3280
+ set_metadata appends without deduping, and the probe path runs inside preprocess, so a second
3281
+ call must never register a duplicate handler; a key already declared keeps its first type.
3282
+ """
3283
+ if not metadata_types:
3284
+ return
3285
+ existing = next((handler for handler in leap_binder.setup_container.metadata
3286
+ if handler.name == "builtin_instance_extra_metadata"), None)
3287
+ if existing is not None:
3288
+ if isinstance(existing.metadata_type, dict):
3289
+ for name, metadata_type in metadata_types.items():
3290
+ existing.metadata_type.setdefault(name, metadata_type)
3291
+ return
3292
+ declared: Dict[str, DatasetMetadataType] = dict(metadata_types)
3293
+
3294
+ def builtin_instance_extra_metadata(idx: str, preprocess: PreprocessResponse) -> Dict[str, None]:
3295
+ # Sample rows declare the columns without claiming values; the engine injects the
3296
+ # real per-instance ones. A fresh dict, because nulling the probed
3297
+ # ElementInstance's own dict in place destroyed any encoder that returned a
3298
+ # reference into preprocess.data. Read at call time, so keys merged in later count.
3299
+ return {name: None for name in declared}
3300
+
3301
+ leap_binder.set_metadata(builtin_instance_extra_metadata,
3302
+ "builtin_instance_extra_metadata", declared)
3303
+
3304
+
3257
3305
  def tensorleap_element_instance_preprocess(
3258
3306
  instance_length_encoder: InstanceLengthCallableInterface,
3259
3307
  instance_mask_encoder: InstanceCallableInterface,
3260
3308
  instance_metadata_types: Optional[Dict[str, DatasetMetadataType]] = None):
3261
3309
  def decorating_function(user_function: Callable[[], List[PreprocessResponse]]):
3262
- def register_instance_extra_metadata(metadata_types: Dict[str, DatasetMetadataType]) -> None:
3263
- # Idempotent: set_metadata appends without deduping, and the probe path runs inside
3264
- # preprocess, so a second preprocess call would register a duplicate handler.
3265
- if any(handler.name == "builtin_instance_extra_metadata"
3266
- for handler in leap_binder.setup_container.metadata):
3267
- return
3268
- names = list(metadata_types)
3269
-
3270
- def builtin_instance_extra_metadata(idx: str, preprocess: PreprocessResponse) -> Dict[str, None]:
3271
- # Sample rows declare the columns without claiming values; the engine injects the
3272
- # real per-instance ones. A fresh dict, because nulling the probed
3273
- # ElementInstance's own dict in place destroyed any encoder that returned a
3274
- # reference into preprocess.data.
3275
- return {name: None for name in names}
3276
-
3277
- leap_binder.set_metadata(builtin_instance_extra_metadata,
3278
- "builtin_instance_extra_metadata", metadata_types)
3279
-
3280
3310
  def resolve_instance_metadata_types(sample_id: Union[int, str], preprocess_response: PreprocessResponse,
3281
3311
  idx: int) -> Optional[Dict[str, DatasetMetadataType]]:
3282
3312
  # The probe enumerates the KEYS; the declaration, where given, supplies the TYPE.
@@ -3346,7 +3376,7 @@ def tensorleap_element_instance_preprocess(
3346
3376
  resolved_types = resolve_instance_metadata_types(
3347
3377
  sample_id, preprocess_response, idx)
3348
3378
  if resolved_types:
3349
- register_instance_extra_metadata(resolved_types)
3379
+ _register_builtin_instance_extra_metadata(resolved_types)
3350
3380
  found_instance_metadata = True
3351
3381
 
3352
3382
  instance_to_sample_ids_mappings[instance_id] = sample_id
@@ -3357,11 +3387,8 @@ def tensorleap_element_instance_preprocess(
3357
3387
  preprocess_response.sample_ids = all_sample_ids
3358
3388
  return result
3359
3389
 
3360
- def builtin_instance_metadata(idx: str, preprocess: PreprocessResponse) -> Dict[str, str]:
3361
- return {'is_instance': '0', 'original_sample_id': idx, 'instance_name': 'none'}
3362
-
3363
3390
  leap_binder.set_preprocess(user_function_instance)
3364
- leap_binder.set_metadata(builtin_instance_metadata, "builtin_instance_metadata")
3391
+ _register_builtin_instance_metadata()
3365
3392
 
3366
3393
  def _validate_input_args(*args, **kwargs):
3367
3394
  assert len(args) == 0 and len(kwargs) == 0, \
@@ -3473,6 +3500,157 @@ def tensorleap_instances_masks_encoder(name: str):
3473
3500
  return decorating_function
3474
3501
 
3475
3502
 
3503
+ def tensorleap_false_positive_instances_encoder(
3504
+ name: str, instance_metadata_types: Optional[Dict[str, DatasetMetadataType]] = None):
3505
+ """Register the false-positive instances encoder.
3506
+
3507
+ `instance_metadata_types` declares the keys (and types) of the `instance_metadata` the returned
3508
+ ElementInstances may carry. They become `builtin_instance_extra_metadata_<key>` columns, merged
3509
+ with any keys the element-instance preprocess declares; a key not declared here (or by the
3510
+ GT instances) is dropped from false-positive rows by the engine.
3511
+ """
3512
+ def decorating_function(user_function: Callable):
3513
+ def _validate_input_args(sample_id, preprocess_response, predictions):
3514
+ assert isinstance(sample_id, (str, int)), \
3515
+ (f'tensorleap_false_positive_instances_encoder validation failed: '
3516
+ f'Argument sample_id should be str or int. Got {type(sample_id)}.')
3517
+ assert isinstance(preprocess_response, PreprocessResponse), \
3518
+ (f'tensorleap_false_positive_instances_encoder validation failed: '
3519
+ f'Argument preprocess_response should be a PreprocessResponse. Got {type(preprocess_response)}.')
3520
+ assert isinstance(predictions, dict), \
3521
+ (f'tensorleap_false_positive_instances_encoder validation failed: '
3522
+ f'Argument predictions should be a dict. Got {type(predictions)}.')
3523
+
3524
+ def _validate_result(result):
3525
+ assert isinstance(result, list), \
3526
+ (f'tensorleap_false_positive_instances_encoder validation failed: '
3527
+ f'The return type should be a list of ElementInstance. Got {type(result)}.')
3528
+ for i, instance in enumerate(result):
3529
+ assert isinstance(instance, ElementInstance), \
3530
+ (f'tensorleap_false_positive_instances_encoder validation failed: '
3531
+ f'Element {i} of the returned list should be an ElementInstance. Got {type(instance)}.')
3532
+
3533
+ def inner_without_validate(sample_id, preprocess_response, predictions):
3534
+ global _called_from_inside_tl_decorator
3535
+ _called_from_inside_tl_decorator += 1
3536
+
3537
+ try:
3538
+ result = user_function(sample_id, preprocess_response, predictions)
3539
+ finally:
3540
+ _called_from_inside_tl_decorator -= 1
3541
+
3542
+ return result
3543
+
3544
+ leap_binder.set_false_positive_instances(inner_without_validate, name)
3545
+ _register_builtin_instance_metadata()
3546
+ if instance_metadata_types:
3547
+ _register_builtin_instance_extra_metadata(instance_metadata_types)
3548
+
3549
+ def inner(sample_id, preprocess_response, predictions):
3550
+ if os.environ.get(mapping_runtime_mode_env_var_mame):
3551
+ return []
3552
+
3553
+ _validate_input_args(sample_id, preprocess_response, predictions)
3554
+
3555
+ result = inner_without_validate(sample_id, preprocess_response, predictions)
3556
+
3557
+ _validate_result(result)
3558
+ return result
3559
+
3560
+ return inner
3561
+
3562
+ return decorating_function
3563
+
3564
+
3565
+ def tensorleap_false_positive_empty_gt(gt_name: str):
3566
+ """Declare the empty ground truth used for false-positive instance rows of GT encoder `gt_name`."""
3567
+ def decorating_function(user_function: Callable):
3568
+ def _validate_input_args(sample_id, preprocess_response):
3569
+ assert isinstance(sample_id, (str, int)), \
3570
+ (f'tensorleap_false_positive_empty_gt validation failed: '
3571
+ f'Argument sample_id should be str or int. Got {type(sample_id)}.')
3572
+ assert isinstance(preprocess_response, PreprocessResponse), \
3573
+ (f'tensorleap_false_positive_empty_gt validation failed: '
3574
+ f'Argument preprocess_response should be a PreprocessResponse. Got {type(preprocess_response)}.')
3575
+
3576
+ def _validate_result(result):
3577
+ assert isinstance(result, np.ndarray), \
3578
+ (f'tensorleap_false_positive_empty_gt validation failed: '
3579
+ f'The return type should be a numpy array. Got {type(result)}.')
3580
+
3581
+ def inner_without_validate(sample_id, preprocess_response):
3582
+ global _called_from_inside_tl_decorator
3583
+ _called_from_inside_tl_decorator += 1
3584
+ try:
3585
+ result = user_function(sample_id, preprocess_response)
3586
+ finally:
3587
+ _called_from_inside_tl_decorator -= 1
3588
+ return result
3589
+
3590
+ leap_binder.set_false_positive_empty_gt(inner_without_validate, gt_name)
3591
+
3592
+ def inner(sample_id, preprocess_response):
3593
+ if os.environ.get(mapping_runtime_mode_env_var_mame):
3594
+ return np.zeros(0, dtype=np.float32)
3595
+
3596
+ _validate_input_args(sample_id, preprocess_response)
3597
+ result = inner_without_validate(sample_id, preprocess_response)
3598
+ _validate_result(result)
3599
+ return result
3600
+
3601
+ return inner
3602
+
3603
+ return decorating_function
3604
+
3605
+
3606
+ def tensorleap_false_positive_custom_latent_space(name: str):
3607
+ """Compute the instance custom latent space `name` (a tensorleap_instance_custom_latent_space)
3608
+ for a false-positive instance. Signature: fn(sample_id, preprocess, fp_instance) -> 1-D np.ndarray."""
3609
+ def decorating_function(user_function: Callable):
3610
+ def _validate_input_args(sample_id, preprocess_response, fp_instance):
3611
+ assert isinstance(sample_id, (str, int)), \
3612
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3613
+ f'Argument sample_id should be str or int. Got {type(sample_id)}.')
3614
+ assert isinstance(preprocess_response, PreprocessResponse), \
3615
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3616
+ f'Argument preprocess_response should be a PreprocessResponse. Got {type(preprocess_response)}.')
3617
+ assert isinstance(fp_instance, ElementInstance), \
3618
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3619
+ f'Argument fp_instance should be an ElementInstance. Got {type(fp_instance)}.')
3620
+
3621
+ def _validate_result(result):
3622
+ assert isinstance(result, np.ndarray), \
3623
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3624
+ f'The return type should be a numpy array. Got {type(result)}.')
3625
+ assert result.ndim == 1, \
3626
+ (f'tensorleap_false_positive_custom_latent_space validation failed: '
3627
+ f'The returned array should be 1-D. Got shape {result.shape}.')
3628
+
3629
+ def inner_without_validate(sample_id, preprocess_response, fp_instance):
3630
+ global _called_from_inside_tl_decorator
3631
+ _called_from_inside_tl_decorator += 1
3632
+ try:
3633
+ result = user_function(sample_id, preprocess_response, fp_instance)
3634
+ finally:
3635
+ _called_from_inside_tl_decorator -= 1
3636
+ return result
3637
+
3638
+ leap_binder.set_false_positive_custom_latent_space(inner_without_validate, name)
3639
+
3640
+ def inner(sample_id, preprocess_response, fp_instance):
3641
+ if os.environ.get(mapping_runtime_mode_env_var_mame):
3642
+ return np.zeros(0, dtype=np.float32)
3643
+
3644
+ _validate_input_args(sample_id, preprocess_response, fp_instance)
3645
+ result = inner_without_validate(sample_id, preprocess_response, fp_instance)
3646
+ _validate_result(result)
3647
+ return result
3648
+
3649
+ return inner
3650
+
3651
+ return decorating_function
3652
+
3653
+
3476
3654
  def tensorleap_instances_length_encoder(name: str):
3477
3655
  def decorating_function(user_function: InstanceLengthCallableInterface):
3478
3656
  def _validate_input_args(sample_id: str, preprocess_response: PreprocessResponse):
@@ -434,6 +434,9 @@ class LeapLoader(LeapLoaderBase):
434
434
  autoregressive_test_payload = self._check_autoregressive_step()
435
435
  if autoregressive_test_payload is not None:
436
436
  test_payloads.append(autoregressive_test_payload)
437
+ false_positive_test_payload = self._check_false_positive_empty_gt()
438
+ if false_positive_test_payload is not None:
439
+ test_payloads.append(false_positive_test_payload)
437
440
  is_valid = all([payload.is_passed for payload in test_payloads])
438
441
  setup_response = self.get_dataset_setup_response(handlers_test_payloads)
439
442
 
@@ -642,6 +645,24 @@ class LeapLoader(LeapLoaderBase):
642
645
  result_payloads.append(test_result)
643
646
  return result_payloads
644
647
 
648
+ def _check_false_positive_empty_gt(self) -> Optional[DatasetTestResultPayload]:
649
+ """Push-time check that every GT encoder has a matching tensorleap_false_positive_empty_gt
650
+ of the right shape. None (no payload) without a false-positive encoder."""
651
+ handler = global_leap_binder.setup_container.false_positive_instances
652
+ if handler is None:
653
+ return None
654
+ test_result = DatasetTestResultPayload(handler.name)
655
+ try:
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)
659
+ except Exception as e:
660
+ line_number, file_name, stacktrace = get_root_exception_file_and_line_number()
661
+ error_string = f"{repr(e)} in file {file_name}, line_number: {line_number}\nStacktrace:\n{stacktrace}"
662
+ test_result.display[TestingSectionEnum.Errors.name] = error_string
663
+ test_result.is_passed = False
664
+ return test_result
665
+
645
666
  def _check_autoregressive_step(self) -> Optional[DatasetTestResultPayload]:
646
667
  handler = global_leap_binder.setup_container.autoregressive_step
647
668
  if handler is None:
@@ -1056,7 +1077,10 @@ class LeapLoader(LeapLoaderBase):
1056
1077
  encoder cannot be masked. Preprocessing has already run by the time this is called (the
1057
1078
  lengths above come from it), so reading the mappings costs nothing extra.
1058
1079
  """
1080
+ self.exec_script()
1059
1081
  setup = global_leap_binder.setup_container
1082
+ if setup.false_positive_instances is not None:
1083
+ return True
1060
1084
  if not setup.instance_masks:
1061
1085
  return False
1062
1086
  try:
@@ -1768,6 +1792,110 @@ class LeapLoader(LeapLoaderBase):
1768
1792
  return [prediction_type.name for prediction_type in
1769
1793
  global_leap_binder.setup_container.prediction_types]
1770
1794
 
1795
+ def has_false_positive_instances(self) -> bool:
1796
+ self.exec_script()
1797
+ return global_leap_binder.setup_container.false_positive_instances is not None
1798
+
1799
+ def _native_sample_id(self, sample_id: Union[str, int]) -> Union[str, int]:
1800
+ # numpy ints / stringified ids from the engine -> the type the preprocess declared.
1801
+ return self.get_sample_id_type()(sample_id)
1802
+
1803
+ def get_false_positive_instances(self, state: DataStateEnum, sample_ids: List[Union[str, int]],
1804
+ predictions: Dict[str, npt.NDArray[np.float32]]
1805
+ ) -> List[Optional[List[ElementInstance]]]:
1806
+ """Run the false-positive encoder once per sample. `predictions` arrays carry a leading
1807
+ batch axis of len(sample_ids); row i is handed to the user function for sample_ids[i].
1808
+
1809
+ Returns one entry per sample id, in order: the sample's list of ElementInstance (`[]` when
1810
+ it has no false positives), or None when the user function raised for that sample or
1811
+ returned something that is not a list of ElementInstance. A failed sample does not fail
1812
+ the others; its error is printed with the sample id. A batch-size mismatch between
1813
+ `predictions` and `sample_ids`, or a missing encoder, still raises: those are caller errors.
1814
+ """
1815
+ self.exec_script()
1816
+ handler = global_leap_binder.setup_container.false_positive_instances
1817
+ if handler is None:
1818
+ raise Exception("No tensorleap_false_positive_instances_encoder is registered.")
1819
+ preprocess_response = self._preprocess_result()[state]
1820
+ for name, array in predictions.items():
1821
+ if len(array) != len(sample_ids):
1822
+ raise Exception(
1823
+ f"predictions['{name}'] has batch size {len(array)} but {len(sample_ids)} sample ids were given.")
1824
+
1825
+ results: List[Optional[List[ElementInstance]]] = []
1826
+ for i, sample_id in enumerate(sample_ids):
1827
+ row = {name: array[i] for name, array in predictions.items()}
1828
+ try:
1829
+ instances = handler.function(self._native_sample_id(sample_id), preprocess_response, row)
1830
+ self._validate_false_positive_instances(instances, set(self._instance_metric_handler_by_name()))
1831
+ except Exception as e:
1832
+ print(f"tensorleap_false_positive_instances_encoder failed for sample {sample_id!r} "
1833
+ f"(state {state.name}): {e!r}")
1834
+ results.append(None)
1835
+ continue
1836
+ results.append(instances)
1837
+ return results
1838
+
1839
+ @staticmethod
1840
+ def _validate_false_positive_instances(instances: Any, registered_instance_metrics: Set[str]) -> None:
1841
+ prefix = "tensorleap_false_positive_instances_encoder validation failed: "
1842
+ if not isinstance(instances, list):
1843
+ raise Exception(f"{prefix}The return type should be a list of ElementInstance. "
1844
+ f"Got {type(instances)}.")
1845
+ for j, instance in enumerate(instances):
1846
+ if not isinstance(instance, ElementInstance):
1847
+ raise Exception(f"{prefix}Element {j} of the returned list should be an ElementInstance. "
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)}.")
1864
+
1865
+ def get_false_positive_empty_gt(self, state: DataStateEnum,
1866
+ sample_id: Union[str, int]) -> Dict[str, npt.NDArray[np.float32]]:
1867
+ self.exec_script()
1868
+ preprocess_response = self._preprocess_result()[state]
1869
+ sample_id = self._native_sample_id(sample_id)
1870
+ empty_gt: Dict[str, npt.NDArray[np.float32]] = {}
1871
+ for gt_name, function in global_leap_binder.setup_container.false_positive_empty_gt.items():
1872
+ value = function(sample_id, preprocess_response)
1873
+ if not isinstance(value, np.ndarray):
1874
+ raise Exception(f"tensorleap_false_positive_empty_gt validation failed: "
1875
+ f"The return type for '{gt_name}' should be a numpy array. Got {type(value)}.")
1876
+ empty_gt[gt_name] = value.astype(np.float32, copy=False)
1877
+ return empty_gt
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
+
1771
1899
  def get_instances_data(self, state: DataStateEnum) -> Tuple[Dict[str, List[str]], Dict[str, str]]:
1772
1900
  preprocess_result = self._preprocess_result()
1773
1901
  preprocess_state = preprocess_result.get(state)
@@ -9,7 +9,7 @@ import numpy.typing as npt
9
9
 
10
10
  from code_loader.contract.datasetclasses import DatasetSample, LeapData, \
11
11
  PredictionTypeHandler, CustomLayerHandler, VisualizerHandlerData, MetricHandlerData, MetricCallableReturnType, \
12
- CustomLossHandlerData
12
+ CustomLossHandlerData, ElementInstance
13
13
  from code_loader.contract.enums import DataStateEnum, DataStateType
14
14
  from code_loader.contract.responsedataclasses import DatasetIntegParseResult, DatasetTestResultPayload, \
15
15
  DatasetSetup, ModelSetup
@@ -72,6 +72,25 @@ class LeapLoaderBase:
72
72
  def get_instances_data(self, state: DataStateEnum) -> Tuple[Dict[str, List[str]], Dict[str, str]]:
73
73
  pass
74
74
 
75
+ # Not abstract: a loader that predates false-positive instances (or a stand-in) has none.
76
+ def has_false_positive_instances(self) -> bool:
77
+ return False
78
+
79
+ def get_false_positive_instances(self, state: DataStateEnum, sample_ids: List[Union[str, int]],
80
+ predictions: Dict[str, npt.NDArray[np.float32]]
81
+ ) -> List[Optional[List[ElementInstance]]]:
82
+ """One entry per sample id: its false-positive instances, or None if the encoder failed
83
+ for that sample (see LeapLoader.get_false_positive_instances)."""
84
+ raise NotImplementedError("get_false_positive_instances is not implemented by this loader")
85
+
86
+ def get_false_positive_empty_gt(self, state: DataStateEnum,
87
+ sample_id: Union[str, int]) -> Dict[str, npt.NDArray[np.float32]]:
88
+ raise NotImplementedError("get_false_positive_empty_gt is not implemented by this loader")
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
+
75
94
  def get_metadata_multiple_samples(self, state: DataStateEnum, sample_ids: Union[List[int], List[str]],
76
95
  requested_metadata_names: Optional[List[str]] = None
77
96
  ) -> Tuple[Dict[str, Union[List[str], List[int], List[bool],
@@ -53,6 +53,22 @@ 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:
57
+ def numpy_encoder_function(idx: Union[int, str], samples: PreprocessResponse, predictions: Dict[str, Any]) -> List[ElementInstance]:
58
+ result = encoder_function(idx, samples, predictions)
59
+ if not isinstance(result, list):
60
+ return result # malformed: the loader reports it with a clear message
61
+ for instance in result:
62
+ if not isinstance(instance, ElementInstance):
63
+ continue
64
+ instance.mask = np.array(instance.mask)
65
+ if instance.instance_metadata is not None and instance.instance_metadata_is_none is None:
66
+ instance.instance_metadata_is_none = {
67
+ k: is_absent_metadata_value(v) for k, v in instance.instance_metadata.items()}
68
+ return result
69
+ return numpy_encoder_function
70
+
71
+
56
72
  # The engine downcasts every tensor to float16 before pushing it to Redis, and metrics and losses
57
73
  # read their tensors back from there — so on the platform they are handed float16 arrays. A local
58
74
  # run feeds them the float32 arrays straight from the encoders and the model, which is why a
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "code-loader"
3
- version = "1.0.212"
3
+ version = "1.0.214"
4
4
  description = ""
5
5
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
6
6
  license = "MIT"
File without changes
File without changes