code-loader 1.0.211__tar.gz → 1.0.214.dev1__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.211 → code_loader-1.0.214.dev1}/PKG-INFO +1 -1
  2. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/contract/datasetclasses.py +7 -0
  3. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/contract/responsedataclasses.py +2 -1
  4. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/contract/visualizer_classes.py +3 -2
  5. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/inner_leap_binder/leapbinder.py +60 -3
  6. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/inner_leap_binder/leapbinder_decorators.py +153 -23
  7. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/leaploader.py +91 -0
  8. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/leaploaderbase.py +16 -1
  9. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/utils.py +16 -0
  10. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/pyproject.toml +1 -1
  11. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/LICENSE +0 -0
  12. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/README.md +0 -0
  13. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/__init__.py +0 -0
  14. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/contract/__init__.py +0 -0
  15. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/contract/enums.py +0 -0
  16. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/contract/exceptions.py +0 -0
  17. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/contract/mapping.py +0 -0
  18. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/contract/sim_config.py +0 -0
  19. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/default_losses.py +0 -0
  20. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/default_metrics.py +0 -0
  21. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/__init__.py +0 -0
  22. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/api.py +0 -0
  23. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/cli_config_utils.py +0 -0
  24. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/client.py +0 -0
  25. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/epoch.py +0 -0
  26. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/experiment.py +0 -0
  27. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/experiment_context.py +0 -0
  28. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/types.py +0 -0
  29. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/utils.py +0 -0
  30. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  31. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/inner_leap_binder/__init__.py +0 -0
  32. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/mixpanel_tracker.py +0 -0
  33. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/plot_functions/__init__.py +0 -0
  34. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/plot_functions/plot_functions.py +0 -0
  35. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/plot_functions/visualize.py +0 -0
  36. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/code_loader/visualizers/__init__.py +0 -0
  37. {code_loader-1.0.211 → code_loader-1.0.214.dev1}/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.211
3
+ Version: 1.0.214.dev1
4
4
  Summary:
5
5
  Home-page: https://github.com/tensorleap/code-loader
6
6
  License: MIT
@@ -307,6 +307,11 @@ class ElementInstanceMasksHandler:
307
307
  name: str
308
308
  function: InstanceCallableInterface
309
309
 
310
+ @dataclass
311
+ class FalsePositiveInstancesHandler:
312
+ name: str
313
+ function: Callable
314
+
310
315
  @dataclass
311
316
  class GroundTruthHandler(DatasetBaseHandler):
312
317
  shape: Optional[List[int]] = None
@@ -405,6 +410,8 @@ class DatasetIntegrationSetup:
405
410
  visualizers: List[VisualizerHandler] = field(default_factory=list)
406
411
  inputs: List[InputHandler] = field(default_factory=list)
407
412
  instance_masks: List[ElementInstanceMasksHandler] = field(default_factory=list)
413
+ false_positive_instances: Optional[FalsePositiveInstancesHandler] = None
414
+ false_positive_empty_gt: Dict[str, Callable] = field(default_factory=dict)
408
415
  ground_truths: List[GroundTruthHandler] = field(default_factory=list)
409
416
  metadata: List[MetadataHandler] = field(default_factory=list)
410
417
  prediction_types: List[PredictionTypeHandler] = field(default_factory=list)
@@ -144,6 +144,7 @@ class BoundingBox3D:
144
144
  yaw (float): Rotation about +z in radians, counter-clockwise from +x. The +length/2 face is the front.
145
145
  label (str): The class name of the box.
146
146
  confidence (float): Prediction score, typically in [0, 1]; 1.0 for ground truth.
147
+ is_ground_truth (bool): True marks a ground-truth box; the UI draws it dashed so it can be shown with predictions.
147
148
  metadata (Optional[Dict[str, Union[str, int, float]]]): Optional extra fields shown with the box.
148
149
 
149
150
  Corners are the local points (±length/2, ±width/2, ±height/2) rotated by yaw about z, then translated to (x, y, z).
@@ -157,10 +158,10 @@ class BoundingBox3D:
157
158
  yaw: float
158
159
  label: str
159
160
  confidence: float = 1.0
161
+ is_ground_truth: bool = False
160
162
  metadata: Optional[Dict[str, Union[str, int, float]]] = None
161
163
 
162
164
 
163
-
164
165
  @dataclass
165
166
  class LeapAnalysisConfiguration:
166
167
  domain_gap_metadata: Optional[List[str]] = None
@@ -395,8 +395,9 @@ class LeapPointCloud:
395
395
 
396
396
  Example:
397
397
  points = (np.random.rand(1000, 3) * 50).astype(np.float32)
398
- box = BoundingBox3D(x=10, y=0, z=1, width=2, length=4.5, height=1.8, yaw=0.3, label="car", confidence=0.9)
399
- leap_point_cloud = LeapPointCloud(points=points, intensity=points[:, 2].copy(), bounding_boxes=[box])
398
+ gt = BoundingBox3D(x=10, y=0, z=1, width=2, length=4.5, height=1.8, yaw=0.3, label="car", is_ground_truth=True)
399
+ pred = BoundingBox3D(x=10.2, y=0.1, z=1, width=2, length=4.4, height=1.8, yaw=0.28, label="car", confidence=0.9)
400
+ leap_point_cloud = LeapPointCloud(points=points, intensity=points[:, 2].copy(), bounding_boxes=[gt, pred])
400
401
  """
401
402
  points: npt.NDArray[np.float32]
402
403
  intensity: Optional[npt.NDArray[np.float32]] = None
@@ -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,57 @@ 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
+
343
395
  def add_custom_loss(self, function: CustomCallableInterface, name: str) -> None:
344
396
  """
345
397
  Add a custom loss function to the setup.
@@ -798,6 +850,10 @@ class LeapBinder:
798
850
  for state, preprocess_response in preprocess_result.items():
799
851
  if preprocess_response.length is None or preprocess_response.length <= 0:
800
852
  raise Exception('Invalid dataset length')
853
+ if preprocess_response.is_grouped and self.setup_container.false_positive_instances is not None:
854
+ raise Exception(
855
+ 'tensorleap_false_positive_instances_encoder is not supported with a grouped '
856
+ f'preprocess response (grouped state: {state.name}). Use flat sample_ids.')
801
857
  if unlabeled_preprocess_handler is not None and state == DataStateEnum.unlabeled:
802
858
  unlabeled_preprocess_handler.data_length = preprocess_response.length
803
859
  else:
@@ -1018,6 +1074,7 @@ class LeapBinder:
1018
1074
  preprocess_result = self.get_preprocess_result()
1019
1075
  self.check_preprocess(preprocess_result)
1020
1076
  self.check_handlers(preprocess_result)
1077
+ self.check_false_positive_empty_gt(preprocess_result)
1021
1078
  self.check_simulations()
1022
1079
  self.validate_ignore_latent_spaces()
1023
1080
  print("Successful!")
@@ -1058,7 +1115,7 @@ class LeapBinder:
1058
1115
  "Simulations are not supported together with tensorleap_autoregressive_step yet: "
1059
1116
  "the autoregressive entry points do not resolve synthetic sample ids. Remove the "
1060
1117
  "simulation decorators or the autoregressive hook.")
1061
- if self.setup_container.instance_masks:
1118
+ if self.setup_container.instance_masks or self.setup_container.false_positive_instances is not None:
1062
1119
  raise Exception(
1063
1120
  "Element instances are not supported together with tensorleap_autoregressive_step: "
1064
1121
  "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,109 @@ 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
+
3476
3606
  def tensorleap_instances_length_encoder(name: str):
3477
3607
  def decorating_function(user_function: InstanceLengthCallableInterface):
3478
3608
  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,22 @@ 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
+ global_leap_binder.check_false_positive_empty_gt(self._preprocess_result())
657
+ except Exception as e:
658
+ line_number, file_name, stacktrace = get_root_exception_file_and_line_number()
659
+ error_string = f"{repr(e)} in file {file_name}, line_number: {line_number}\nStacktrace:\n{stacktrace}"
660
+ test_result.display[TestingSectionEnum.Errors.name] = error_string
661
+ test_result.is_passed = False
662
+ return test_result
663
+
645
664
  def _check_autoregressive_step(self) -> Optional[DatasetTestResultPayload]:
646
665
  handler = global_leap_binder.setup_container.autoregressive_step
647
666
  if handler is None:
@@ -1056,7 +1075,10 @@ class LeapLoader(LeapLoaderBase):
1056
1075
  encoder cannot be masked. Preprocessing has already run by the time this is called (the
1057
1076
  lengths above come from it), so reading the mappings costs nothing extra.
1058
1077
  """
1078
+ self.exec_script()
1059
1079
  setup = global_leap_binder.setup_container
1080
+ if setup.false_positive_instances is not None:
1081
+ return True
1060
1082
  if not setup.instance_masks:
1061
1083
  return False
1062
1084
  try:
@@ -1768,6 +1790,75 @@ class LeapLoader(LeapLoaderBase):
1768
1790
  return [prediction_type.name for prediction_type in
1769
1791
  global_leap_binder.setup_container.prediction_types]
1770
1792
 
1793
+ def has_false_positive_instances(self) -> bool:
1794
+ self.exec_script()
1795
+ return global_leap_binder.setup_container.false_positive_instances is not None
1796
+
1797
+ def _native_sample_id(self, sample_id: Union[str, int]) -> Union[str, int]:
1798
+ # numpy ints / stringified ids from the engine -> the type the preprocess declared.
1799
+ return self.get_sample_id_type()(sample_id)
1800
+
1801
+ def get_false_positive_instances(self, state: DataStateEnum, sample_ids: List[Union[str, int]],
1802
+ predictions: Dict[str, npt.NDArray[np.float32]]
1803
+ ) -> List[Optional[List[ElementInstance]]]:
1804
+ """Run the false-positive encoder once per sample. `predictions` arrays carry a leading
1805
+ batch axis of len(sample_ids); row i is handed to the user function for sample_ids[i].
1806
+
1807
+ Returns one entry per sample id, in order: the sample's list of ElementInstance (`[]` when
1808
+ it has no false positives), or None when the user function raised for that sample or
1809
+ returned something that is not a list of ElementInstance. A failed sample does not fail
1810
+ the others; its error is printed with the sample id. A batch-size mismatch between
1811
+ `predictions` and `sample_ids`, or a missing encoder, still raises: those are caller errors.
1812
+ """
1813
+ self.exec_script()
1814
+ handler = global_leap_binder.setup_container.false_positive_instances
1815
+ if handler is None:
1816
+ raise Exception("No tensorleap_false_positive_instances_encoder is registered.")
1817
+ preprocess_response = self._preprocess_result()[state]
1818
+ for name, array in predictions.items():
1819
+ if len(array) != len(sample_ids):
1820
+ raise Exception(
1821
+ f"predictions['{name}'] has batch size {len(array)} but {len(sample_ids)} sample ids were given.")
1822
+
1823
+ results: List[Optional[List[ElementInstance]]] = []
1824
+ for i, sample_id in enumerate(sample_ids):
1825
+ row = {name: array[i] for name, array in predictions.items()}
1826
+ try:
1827
+ instances = handler.function(self._native_sample_id(sample_id), preprocess_response, row)
1828
+ self._validate_false_positive_instances(instances)
1829
+ except Exception as e:
1830
+ print(f"tensorleap_false_positive_instances_encoder failed for sample {sample_id!r} "
1831
+ f"(state {state.name}): {e!r}")
1832
+ results.append(None)
1833
+ continue
1834
+ results.append(instances)
1835
+ return results
1836
+
1837
+ @staticmethod
1838
+ def _validate_false_positive_instances(instances: Any) -> None:
1839
+ prefix = "tensorleap_false_positive_instances_encoder validation failed: "
1840
+ if not isinstance(instances, list):
1841
+ raise Exception(f"{prefix}The return type should be a list of ElementInstance. "
1842
+ f"Got {type(instances)}.")
1843
+ for j, instance in enumerate(instances):
1844
+ if not isinstance(instance, ElementInstance):
1845
+ raise Exception(f"{prefix}Element {j} of the returned list should be an ElementInstance. "
1846
+ f"Got {type(instance)}.")
1847
+
1848
+ def get_false_positive_empty_gt(self, state: DataStateEnum,
1849
+ sample_id: Union[str, int]) -> Dict[str, npt.NDArray[np.float32]]:
1850
+ self.exec_script()
1851
+ preprocess_response = self._preprocess_result()[state]
1852
+ sample_id = self._native_sample_id(sample_id)
1853
+ empty_gt: Dict[str, npt.NDArray[np.float32]] = {}
1854
+ for gt_name, function in global_leap_binder.setup_container.false_positive_empty_gt.items():
1855
+ value = function(sample_id, preprocess_response)
1856
+ if not isinstance(value, np.ndarray):
1857
+ raise Exception(f"tensorleap_false_positive_empty_gt validation failed: "
1858
+ f"The return type for '{gt_name}' should be a numpy array. Got {type(value)}.")
1859
+ empty_gt[gt_name] = value.astype(np.float32, copy=False)
1860
+ return empty_gt
1861
+
1771
1862
  def get_instances_data(self, state: DataStateEnum) -> Tuple[Dict[str, List[str]], Dict[str, str]]:
1772
1863
  preprocess_result = self._preprocess_result()
1773
1864
  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,21 @@ 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
+
75
90
  def get_metadata_multiple_samples(self, state: DataStateEnum, sample_ids: Union[List[int], List[str]],
76
91
  requested_metadata_names: Optional[List[str]] = None
77
92
  ) -> 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.211"
3
+ version = "1.0.214.dev1"
4
4
  description = ""
5
5
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
6
6
  license = "MIT"