code-loader 1.0.199__tar.gz → 1.0.201__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.199 → code_loader-1.0.201}/PKG-INFO +1 -1
  2. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/contract/datasetclasses.py +7 -5
  3. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/contract/visualizer_classes.py +3 -10
  4. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/inner_leap_binder/leapbinder.py +42 -6
  5. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/inner_leap_binder/leapbinder_decorators.py +11 -4
  6. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/leaploader.py +55 -28
  7. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/leaploaderbase.py +8 -0
  8. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/visualizers/default_visualizers.py +21 -28
  9. {code_loader-1.0.199 → code_loader-1.0.201}/pyproject.toml +1 -1
  10. {code_loader-1.0.199 → code_loader-1.0.201}/LICENSE +0 -0
  11. {code_loader-1.0.199 → code_loader-1.0.201}/README.md +0 -0
  12. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/__init__.py +0 -0
  13. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/contract/__init__.py +0 -0
  14. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/contract/enums.py +0 -0
  15. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/contract/exceptions.py +0 -0
  16. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/contract/mapping.py +0 -0
  17. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/contract/responsedataclasses.py +0 -0
  18. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/contract/sim_config.py +0 -0
  19. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/default_losses.py +0 -0
  20. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/default_metrics.py +0 -0
  21. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/__init__.py +0 -0
  22. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/api.py +0 -0
  23. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/cli_config_utils.py +0 -0
  24. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/client.py +0 -0
  25. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/epoch.py +0 -0
  26. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/experiment.py +0 -0
  27. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/experiment_context.py +0 -0
  28. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/types.py +0 -0
  29. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/utils.py +0 -0
  30. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  31. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/inner_leap_binder/__init__.py +0 -0
  32. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/mixpanel_tracker.py +0 -0
  33. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/plot_functions/__init__.py +0 -0
  34. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/plot_functions/plot_functions.py +0 -0
  35. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/plot_functions/visualize.py +0 -0
  36. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/utils.py +0 -0
  37. {code_loader-1.0.199 → code_loader-1.0.201}/code_loader/visualizers/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: code-loader
3
- Version: 1.0.199
3
+ Version: 1.0.201
4
4
  Summary:
5
5
  Home-page: https://github.com/tensorleap/code-loader
6
6
  License: MIT
@@ -8,7 +8,7 @@ import numpy.typing as npt
8
8
  from code_loader.contract.enums import DataStateType, DataStateEnum, LeapDataType, ConfusionMatrixValue, \
9
9
  MetricDirection, DatasetMetadataType
10
10
  from code_loader.contract.visualizer_classes import LeapImage, LeapText, LeapGraph, LeapHorizontalBar, \
11
- LeapTextMask, LeapImageMask, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo
11
+ LeapTextMask, LeapImageMask, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapAudio
12
12
  from code_loader.contract.sim_config import SimConfig
13
13
 
14
14
  custom_latent_space_attribute = "custom_latent_space"
@@ -194,11 +194,12 @@ VisualizerCallableInterface = Union[
194
194
  Callable[..., LeapImageMask],
195
195
  Callable[..., LeapTextMask],
196
196
  Callable[..., LeapImageWithBBox],
197
- Callable[..., LeapImageWithHeatmap]
197
+ Callable[..., LeapImageWithHeatmap],
198
+ Callable[..., LeapAudio]
198
199
  ]
199
200
 
200
201
  LeapData = Union[LeapImage, LeapText, LeapGraph, LeapHorizontalBar, LeapImageMask, LeapTextMask, LeapImageWithBBox,
201
- LeapImageWithHeatmap, LeapVideo]
202
+ LeapImageWithHeatmap, LeapVideo, LeapAudio]
202
203
 
203
204
  CustomCallableInterface = Callable[..., Any]
204
205
 
@@ -308,6 +309,7 @@ class MetadataHandler:
308
309
  class CustomLatentSpaceHandler:
309
310
  function: SectionCallableInterface
310
311
  name: str = 'custom_latent_space'
312
+ use_ls_for_analysis: bool = False
311
313
 
312
314
 
313
315
  # How a chain's latent-space vectors are derived from its per-step forward passes.
@@ -390,7 +392,7 @@ class DatasetIntegrationSetup:
390
392
  metrics: List[MetricHandler] = field(default_factory=list)
391
393
  instance_metrics: List[InstanceMetricHandler] = field(default_factory=list)
392
394
  custom_layers: Dict[str, CustomLayerHandler] = field(default_factory=dict)
393
- custom_latent_space: Optional[CustomLatentSpaceHandler] = None
395
+ custom_latent_spaces: Dict[str, CustomLatentSpaceHandler] = field(default_factory=dict)
394
396
  simulations: List[SimulationHandler] = field(default_factory=list)
395
397
  autoregressive_step: Optional[AutoregressiveStepHandler] = None
396
398
  autoregressive_metrics: List[AutoregressiveMetricHandler] = field(default_factory=list)
@@ -406,6 +408,6 @@ class DatasetSample:
406
408
  metadata_is_none: Dict[str, bool]
407
409
  index: Union[int, str]
408
410
  state: DataStateEnum
409
- custom_latent_space: Optional[npt.NDArray[np.float32]] = None
411
+ custom_latent_spaces: Optional[Dict[str, npt.NDArray[np.float32]]] = None
410
412
  instance_masks: Optional[Dict[str, ElementInstance]] = None
411
413
 
@@ -357,16 +357,14 @@ class LeapAudio:
357
357
  should be mixed down before constructing LeapAudio.
358
358
  sample_rate (int): Samples per second — needed to encode the clip and to map
359
359
  the playhead position to time.
360
- visual (Union[LeapImage, LeapGraph]): The thing you look at.
361
- x_range (Optional[Tuple[float, float]]): Time extent (in seconds) that the
362
- visual's horizontal axis spans, used to align the tick. Defaults to
363
- (0, N / sample_rate).
360
+ visual (Union[LeapImage, LeapGraph]): The thing you look at. For a waveform
361
+ the nested LeapGraph carries its own x_range (time span); the playhead is
362
+ driven by the audio element's currentTime, so no time range is needed here.
364
363
  type (LeapDataType): The data type, default is LeapDataType.Audio.
365
364
  """
366
365
  audio: npt.NDArray
367
366
  sample_rate: int
368
367
  visual: Union[LeapImage, LeapGraph]
369
- x_range: Optional[Tuple[float, float]] = None
370
368
  type: LeapDataType = LeapDataType.Audio
371
369
 
372
370
  def __post_init__(self) -> None:
@@ -377,11 +375,6 @@ class LeapAudio:
377
375
  'Audio visual must be a LeapImage (spectrogram) or LeapGraph (waveform)')
378
376
  if not isinstance(self.sample_rate, int) or self.sample_rate <= 0:
379
377
  raise LeapValidationError(f'sample_rate must be a positive int, got {self.sample_rate}')
380
- if self.x_range is not None:
381
- if len(self.x_range) != 2:
382
- raise LeapValidationError('x_range must be a tuple of length 2')
383
- validate_type(type(self.x_range[0]), [float, int], 'x_range must be a tuple of floats or integers')
384
- validate_type(type(self.x_range[1]), [float, int], 'x_range must be a tuple of floats or integers')
385
378
 
386
379
 
387
380
  map_leap_data_type_to_visualizer_class = {
@@ -536,18 +536,54 @@ class LeapBinder:
536
536
  """
537
537
  self.setup_container.metadata.append(MetadataHandler(name, function, metadata_type))
538
538
 
539
- def set_custom_latent_space(self, function: SectionCallableInterface) -> None:
539
+ def set_custom_latent_space(self, function: SectionCallableInterface,
540
+ name: Optional[str] = None,
541
+ use_ls_for_analysis: bool = False) -> None:
540
542
  """
541
- Set a custom latent space function.
543
+ Register a custom latent space function.
544
+
545
+ Multiple custom latent spaces may be registered as long as each has a
546
+ unique name. They are stored name-keyed (insertion order preserved), so a
547
+ later registration no longer overrides an earlier one.
542
548
 
543
549
  Args:
544
- function (SectionCallableInterface): The metadata handler function.
550
+ function (SectionCallableInterface): The latent-space handler function.
545
551
  This function receives:
546
552
  subset (PreprocessResponse): The subset of the data.
547
553
  index (int): The index of the sample within the subset.
548
- This function should numpy float32 array contains the latent space vec of the sample.
554
+ This function should return a numpy float32 array containing the latent
555
+ space vec of the sample.
556
+ name (Optional[str]): Unique name for this custom latent space. Defaults to
557
+ the reserved single-LS name for backward compatibility.
558
+ use_ls_for_analysis (bool): When True, the engine uses this custom latent
559
+ space for the Out-Of-Distribution and Domain-Gap insights instead of the
560
+ built-in defaults. At most one registered custom latent space may set this;
561
+ registering a second one with the flag raises.
549
562
  """
550
- self.setup_container.custom_latent_space = CustomLatentSpaceHandler(function)
563
+ if name is None:
564
+ name = custom_latent_space_attribute
565
+ if name in self.setup_container.custom_latent_spaces:
566
+ raise Exception(
567
+ f"A custom latent space named '{name}' is already registered. Each "
568
+ f"@tensorleap_custom_latent_space must have a unique name "
569
+ f"(pass name='...' to distinguish them)."
570
+ )
571
+ if use_ls_for_analysis:
572
+ already_flagged = [
573
+ existing_name
574
+ for existing_name, handler in self.setup_container.custom_latent_spaces.items()
575
+ if handler.use_ls_for_analysis
576
+ ]
577
+ if already_flagged:
578
+ raise Exception(
579
+ f"use_ls_for_analysis=True is already set on custom latent space "
580
+ f"'{already_flagged[0]}'. Only one custom latent space may set "
581
+ f"use_ls_for_analysis=True (it selects the latent space used for the "
582
+ f"Out-Of-Distribution and Domain-Gap insights). Set it on '{name}' "
583
+ f"or '{already_flagged[0]}', not both."
584
+ )
585
+ self.setup_container.custom_latent_spaces[name] = CustomLatentSpaceHandler(
586
+ function, name, use_ls_for_analysis)
551
587
 
552
588
  def set_autoregressive_step(self, function: AutoregressiveStepCallableInterface,
553
589
  latent_space_aggregation: str = 'last_step') -> None:
@@ -958,7 +994,7 @@ class LeapBinder:
958
994
  "Element instances are not supported together with tensorleap_autoregressive_step: "
959
995
  "instance generation masks the sample's encoded inputs, which do not exist in an "
960
996
  "autoregressive integration. Remove the instance encoders or the autoregressive hook.")
961
- if self.setup_container.custom_latent_space is not None:
997
+ if self.setup_container.custom_latent_spaces:
962
998
  raise Exception(
963
999
  "tensorleap_custom_latent_space is not supported together with "
964
1000
  "tensorleap_autoregressive_step: the custom latent space function only sees "
@@ -1703,8 +1703,14 @@ def tensorleap_metadata(
1703
1703
  return decorating_function
1704
1704
 
1705
1705
 
1706
- def tensorleap_custom_latent_space():
1706
+ def tensorleap_custom_latent_space(name: Optional[str] = None, use_ls_for_analysis: bool = False):
1707
+ assert isinstance(use_ls_for_analysis, bool), \
1708
+ ("tensorleap_custom_latent_space validation failed: use_ls_for_analysis must be a bool. "
1709
+ f"Got {type(use_ls_for_analysis)}.")
1710
+
1707
1711
  def decorating_function(user_function: SectionCallableInterface):
1712
+ ls_name = name if name is not None else user_function.__name__
1713
+
1708
1714
  def _validate_input_args(sample_id: Union[int, str, list], preprocess_response: PreprocessResponse):
1709
1715
  _validate_id_or_group(sample_id, preprocess_response, 'tensorleap_custom_latent_space')
1710
1716
 
@@ -1715,9 +1721,9 @@ def tensorleap_custom_latent_space():
1715
1721
  if single_result.ndim > 1:
1716
1722
  flat_dim = int(np.prod(single_result.shape))
1717
1723
  store_general_warning(
1718
- key=("tensorleap_custom_latent_space_flatten", tuple(single_result.shape)),
1724
+ key=("tensorleap_custom_latent_space_flatten", ls_name, tuple(single_result.shape)),
1719
1725
  message=(
1720
- f"tensorleap_custom_latent_space returned per-sample shape {tuple(single_result.shape)} "
1726
+ f"tensorleap_custom_latent_space '{ls_name}' returned per-sample shape {tuple(single_result.shape)} "
1721
1727
  f"(ndim={single_result.ndim}). Tensorleap assumes per-sample shape (d, ...) and will "
1722
1728
  f"flatten to ({flat_dim},) before downstream visualization and clustering. "
1723
1729
  f"If you want a different aggregation (e.g. global average pooling), do it "
@@ -1760,7 +1766,8 @@ def tensorleap_custom_latent_space():
1760
1766
 
1761
1767
  return result
1762
1768
 
1763
- leap_binder.set_custom_latent_space(inner_without_validate)
1769
+ leap_binder.set_custom_latent_space(inner_without_validate, ls_name,
1770
+ use_ls_for_analysis=use_ls_for_analysis)
1764
1771
 
1765
1772
  def inner(sample_id, preprocess_response):
1766
1773
  if os.environ.get(mapping_runtime_mode_env_var_mame):
@@ -283,17 +283,7 @@ class LeapLoader(LeapLoaderBase):
283
283
 
284
284
  metadata, metadata_is_none = self.get_metadata(state, sample_id)
285
285
 
286
- custom_latent_space = None
287
- if global_leap_binder.setup_container.custom_latent_space is not None:
288
- latent_fn = global_leap_binder.setup_container.custom_latent_space.function
289
- preprocess_state = preprocess_result[state]
290
- if preprocess_state.is_grouped:
291
- # Single-sample fetch: encode this sample only, not the whole group (same
292
- # memory rationale as _get_dataset_handlers; see grouped-fetch-oom-bug.md).
293
- self._locate_group(preprocess_state, sample_id) # validates group membership
294
- custom_latent_space = self._to_grouped_list(latent_fn([sample_id], preprocess_state))[0]
295
- else:
296
- custom_latent_space = latent_fn(sample_id, preprocess_state)
286
+ custom_latent_spaces = self._get_custom_latent_spaces(sample_id, preprocess_result[state])
297
287
  instance_mask = self._get_instances_masks(state, sample_id, instance_id)
298
288
  sample = DatasetSample(inputs=self._get_inputs(state, sample_id),
299
289
  gt=None if state == DataStateEnum.unlabeled else self._get_gt(state, sample_id),
@@ -301,7 +291,7 @@ class LeapLoader(LeapLoaderBase):
301
291
  metadata_is_none=metadata_is_none,
302
292
  index=sample_id,
303
293
  state=state,
304
- custom_latent_space=custom_latent_space,
294
+ custom_latent_spaces=custom_latent_spaces,
305
295
  instance_masks=instance_mask)
306
296
  return sample
307
297
 
@@ -332,11 +322,7 @@ class LeapLoader(LeapLoaderBase):
332
322
  handler.name, handler_result
333
323
  )
334
324
 
335
- custom_latent_space = None
336
- if global_leap_binder.setup_container.custom_latent_space is not None:
337
- custom_latent_space = global_leap_binder.setup_container.custom_latent_space.function(
338
- original_sample_id, preprocess
339
- )
325
+ custom_latent_spaces = self._get_custom_latent_spaces(original_sample_id, preprocess)
340
326
 
341
327
  return DatasetSample(
342
328
  inputs=inputs,
@@ -345,7 +331,7 @@ class LeapLoader(LeapLoaderBase):
345
331
  metadata_is_none=metadata_is_none,
346
332
  index=synthetic_index,
347
333
  state=DataStateEnum.additional,
348
- custom_latent_space=custom_latent_space,
334
+ custom_latent_spaces=custom_latent_spaces,
349
335
  instance_masks=None,
350
336
  )
351
337
 
@@ -1114,17 +1100,20 @@ class LeapLoader(LeapLoaderBase):
1114
1100
  metadata[handler.name] = [v for v, _ in converted]
1115
1101
  metadata_is_none[handler.name] = [is_none for _, is_none in converted]
1116
1102
 
1117
- # custom_latent_space is group-aware like the input/GT encoders: hand it the whole group in a
1118
- # single call so a file-backed latent fn loads the file once, and normalize to (B, d). This
1119
- # keeps the mandatory group path non-lossy. instance_masks stay None here: they are a
1120
- # per-(sample, instance) concern that needs an instance_id the group fetch does not carry.
1121
- custom_latent_space = None
1122
- if global_leap_binder.setup_container.custom_latent_space is not None:
1123
- latent_fn = global_leap_binder.setup_container.custom_latent_space.function
1124
- custom_latent_space = self._to_grouped_list(latent_fn(group_ids, preprocess_state))
1103
+ # custom_latent_spaces are group-aware like the input/GT encoders: hand each the whole group
1104
+ # in a single call so a file-backed latent fn loads the file once, and normalize to per-sample
1105
+ # lists. This keeps the mandatory group path non-lossy. instance_masks stay None here: they are
1106
+ # a per-(sample, instance) concern that needs an instance_id the group fetch does not carry.
1107
+ latent_handlers = global_leap_binder.setup_container.custom_latent_spaces
1108
+ custom_latent_spaces = None
1109
+ if latent_handlers:
1110
+ custom_latent_spaces = {
1111
+ name: self._to_grouped_list(handler.function(group_ids, preprocess_state))
1112
+ for name, handler in latent_handlers.items()
1113
+ }
1125
1114
 
1126
1115
  return DatasetSample(inputs=inputs, gt=gt, metadata=metadata, metadata_is_none=metadata_is_none,
1127
- index=list(group_ids), state=state, custom_latent_space=custom_latent_space,
1116
+ index=list(group_ids), state=state, custom_latent_spaces=custom_latent_spaces,
1128
1117
  instance_masks=None)
1129
1118
 
1130
1119
  def _get_inputs(self, state: DataStateEnum, sample_id: Union[int, str]) -> Dict[str, npt.NDArray[np.float32]]:
@@ -1337,10 +1326,48 @@ class LeapLoader(LeapLoaderBase):
1337
1326
 
1338
1327
  return id_type
1339
1328
 
1329
+ def _get_custom_latent_spaces(
1330
+ self,
1331
+ sample_id: Union[int, str],
1332
+ preprocess: "PreprocessResponse") -> Optional[Dict[str, npt.NDArray[np.float32]]]:
1333
+ handlers = global_leap_binder.setup_container.custom_latent_spaces
1334
+ if not handlers:
1335
+ return None
1336
+ if preprocess.is_grouped:
1337
+ # Single-sample fetch: encode this sample only, not the whole group (same
1338
+ # memory rationale as _get_dataset_handlers; see grouped-fetch-oom-bug.md).
1339
+ self._locate_group(preprocess, sample_id) # validates group membership
1340
+ return {name: self._to_grouped_list(handler.function([sample_id], preprocess))[0]
1341
+ for name, handler in handlers.items()}
1342
+ return {name: handler.function(sample_id, preprocess) for name, handler in handlers.items()}
1343
+
1340
1344
  @lru_cache()
1341
1345
  def has_custom_latent_space_decorator(self) -> bool:
1342
1346
  self.exec_script()
1343
- return global_leap_binder.setup_container.custom_latent_space is not None
1347
+ return len(global_leap_binder.setup_container.custom_latent_spaces) > 0
1348
+
1349
+ @lru_cache()
1350
+ def get_custom_latent_space_names(self) -> Tuple[str, ...]:
1351
+ """Ordered names of all registered custom latent spaces.
1352
+
1353
+ Registration order is the canonical index mapping consumed by the engine
1354
+ (name i -> user_custom_i). Returns a tuple so the lru_cache value is hashable.
1355
+ """
1356
+ self.exec_script()
1357
+ return tuple(global_leap_binder.setup_container.custom_latent_spaces.keys())
1358
+
1359
+ @lru_cache()
1360
+ def get_custom_latent_space_for_analysis(self) -> Optional[str]:
1361
+ """Name of the custom latent space flagged with use_ls_for_analysis=True, if any.
1362
+
1363
+ The engine uses this latent space for the Out-Of-Distribution and Domain-Gap
1364
+ insights instead of the built-in defaults. Returns None when no custom latent
1365
+ space set the flag. At most one is flagged (enforced at registration time)."""
1366
+ self.exec_script()
1367
+ for name, handler in global_leap_binder.setup_container.custom_latent_spaces.items():
1368
+ if handler.use_ls_for_analysis:
1369
+ return name
1370
+ return None
1344
1371
 
1345
1372
  @lru_cache()
1346
1373
  def has_autoregressive_step(self) -> bool:
@@ -218,6 +218,14 @@ class LeapLoaderBase:
218
218
  raise NotImplementedError(f'{type(self).__name__} does not implement '
219
219
  'autoregressive_visualizer_by_name.')
220
220
 
221
+ @abstractmethod
222
+ def get_custom_latent_space_names(self) -> Tuple[str, ...]:
223
+ pass
224
+
225
+ @abstractmethod
226
+ def get_custom_latent_space_for_analysis(self) -> Optional[str]:
227
+ pass
228
+
221
229
  @abstractmethod
222
230
  def get_heatmap_visualizer_raw_vis_input_arg_name(self, visualizer_name: str) -> Optional[str]:
223
231
  pass
@@ -31,7 +31,6 @@ def default_video_visualizer(data: npt.NDArray[np.float32]) -> LeapVideo:
31
31
 
32
32
 
33
33
  _MAX_WAVEFORM_POINTS = 2000
34
- _VISUAL_KINDS = ('waveform', 'spectrogram', 'image')
35
34
 
36
35
 
37
36
  def _to_mono(audio: npt.NDArray) -> npt.NDArray:
@@ -54,49 +53,43 @@ def _waveform_graph(series: npt.NDArray, duration: float) -> LeapGraph:
54
53
  def default_audio_visualizer(audio: npt.NDArray,
55
54
  sample_rate: int,
56
55
  visual: Optional[npt.NDArray] = None,
57
- visual_kind: Optional[str] = None,
58
56
  time_axis_index: int = -1) -> LeapAudio:
59
57
  """Package a playable audio sample plus the visual a playhead runs through.
60
58
 
61
59
  audio: waveform samples (mono [N], or multi-channel that is mixed down).
62
60
  sample_rate: samples per second.
63
- visual: the array to display. For 'waveform' a 1-D series; for 'spectrogram'
64
- a raw 2-D [freq, time] (or [time, freq]) array to be rendered grayscale;
65
- for 'image' an already-rendered [H, W, 1] or [H, W, 3] image (e.g. a
66
- spectrogram the caller already ran through their own colormap).
61
+ visual: the array to display; its kind is deduced from its shape.
62
+ 1-D [N] -> waveform graph of the samples.
63
+ 2-D [H, W] -> spectrogram, rendered grayscale.
64
+ 3-D [H, W, C] -> already-rendered image; C must be 3 (rgb) or 1 (grayscale).
67
65
  If omitted, a waveform graph is built from `audio` itself.
68
- visual_kind: 'waveform', 'spectrogram', or 'image'. The caller states this
69
- explicitly; the shape of `visual` is never inspected to guess it.
70
- Required when `visual` is given; ignored when `visual` is omitted.
71
- time_axis_index: for 'spectrogram', which axis of `visual` is time (moved to columns).
72
- Unused for 'waveform'/'image'.
66
+ time_axis_index: for a spectrogram, which axis of `visual` is time (moved to columns).
67
+ Unused otherwise.
73
68
  """
74
69
  mono = _to_mono(audio)
75
70
  duration = mono.shape[0] / float(sample_rate)
76
- x_range = (0.0, duration)
77
71
 
78
72
  if visual is None: # no visual given: show the raw signal as a waveform
79
- return LeapAudio(mono, sample_rate, _waveform_graph(mono, duration), x_range)
80
-
81
- if visual_kind not in _VISUAL_KINDS:
82
- raise ValueError(f"visual_kind must be one of {_VISUAL_KINDS}, got {visual_kind!r}")
73
+ return LeapAudio(mono, sample_rate, _waveform_graph(mono, duration))
83
74
 
84
75
  v = np.asarray(visual)
85
76
 
86
- if visual_kind == 'waveform':
87
- return LeapAudio(mono, sample_rate, _waveform_graph(v, duration), x_range)
77
+ if v.ndim == 1: # waveform
78
+ return LeapAudio(mono, sample_rate, _waveform_graph(v, duration))
79
+
80
+ if v.ndim == 3: # already-rendered image
81
+ if v.shape[-1] not in (1, 3):
82
+ raise ValueError(f'image visual must have 1 (grayscale) or 3 (rgb) channels, got shape {v.shape}')
83
+ return LeapAudio(mono, sample_rate, LeapImage(v))
88
84
 
89
- if visual_kind == 'image':
90
- return LeapAudio(mono, sample_rate, LeapImage(v), x_range)
85
+ if v.ndim == 2: # spectrogram
86
+ spec = v
87
+ if time_axis_index % 2 == 0: # move time onto the horizontal axis (columns)
88
+ spec = spec.T
89
+ img = rescale_min_max(spec.astype(np.float32))[..., None] # [freq, time, 1]
90
+ return LeapAudio(mono, sample_rate, LeapImage(img))
91
91
 
92
- # spectrogram
93
- spec = np.squeeze(v)
94
- if spec.ndim != 2:
95
- raise ValueError(f'spectrogram visual must be 2-D (freq x time), got shape {v.shape}')
96
- if time_axis_index % spec.ndim == 0: # move time onto the horizontal axis (columns)
97
- spec = spec.T
98
- img = rescale_min_max(spec.astype(np.float32))[..., None] # [freq, time, 1]
99
- return LeapAudio(mono, sample_rate, LeapImage(img), x_range)
92
+ raise ValueError(f'visual must be 1-D, 2-D, or 3-D, got shape {v.shape}')
100
93
 
101
94
 
102
95
  def default_graph_visualizer(data: npt.NDArray[np.float32]) -> LeapGraph:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "code-loader"
3
- version = "1.0.199"
3
+ version = "1.0.201"
4
4
  description = ""
5
5
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
6
6
  license = "MIT"
File without changes
File without changes