code-loader 1.0.209__tar.gz → 1.0.210__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.209 → code_loader-1.0.210}/PKG-INFO +3 -4
  2. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/contract/datasetclasses.py +2 -2
  3. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/contract/enums.py +1 -0
  4. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/contract/visualizer_classes.py +56 -1
  5. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/inner_leap_binder/leapbinder_decorators.py +3 -2
  6. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/plot_functions/plot_functions.py +24 -1
  7. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/visualizers/default_visualizers.py +15 -1
  8. {code_loader-1.0.209 → code_loader-1.0.210}/pyproject.toml +1 -1
  9. {code_loader-1.0.209 → code_loader-1.0.210}/LICENSE +0 -0
  10. {code_loader-1.0.209 → code_loader-1.0.210}/README.md +0 -0
  11. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/__init__.py +0 -0
  12. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/contract/__init__.py +0 -0
  13. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/contract/exceptions.py +0 -0
  14. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/contract/mapping.py +0 -0
  15. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/contract/responsedataclasses.py +0 -0
  16. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/contract/sim_config.py +0 -0
  17. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/default_losses.py +0 -0
  18. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/default_metrics.py +0 -0
  19. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/__init__.py +0 -0
  20. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/api.py +0 -0
  21. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/cli_config_utils.py +0 -0
  22. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/client.py +0 -0
  23. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/epoch.py +0 -0
  24. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/experiment.py +0 -0
  25. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/experiment_context.py +0 -0
  26. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/types.py +0 -0
  27. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/utils.py +0 -0
  28. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/experiment_api/workingspace_config_utils.py +0 -0
  29. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/inner_leap_binder/__init__.py +0 -0
  30. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/inner_leap_binder/leapbinder.py +0 -0
  31. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/leaploader.py +0 -0
  32. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/leaploaderbase.py +0 -0
  33. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/mixpanel_tracker.py +0 -0
  34. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/plot_functions/__init__.py +0 -0
  35. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/plot_functions/visualize.py +0 -0
  36. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/utils.py +0 -0
  37. {code_loader-1.0.209 → code_loader-1.0.210}/code_loader/visualizers/__init__.py +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: code-loader
3
- Version: 1.0.209
3
+ Version: 1.0.210
4
4
  Summary:
5
+ Home-page: https://github.com/tensorleap/code-loader
5
6
  License: MIT
6
- License-File: LICENSE
7
7
  Author: dorhar
8
8
  Author-email: doron.harnoy@tensorleap.ai
9
9
  Requires-Python: >=3.8,<3.13
@@ -20,7 +20,6 @@ Requires-Dist: numpy (>=2.3.2,<3.0.0) ; python_version >= "3.11" and python_vers
20
20
  Requires-Dist: psutil (>=5.9.5,<6.0.0)
21
21
  Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
22
22
  Requires-Dist: requests (>=2.32.3,<3.0.0)
23
- Project-URL: Homepage, https://github.com/tensorleap/code-loader
24
23
  Project-URL: Repository, https://github.com/tensorleap/code-loader
25
24
  Description-Content-Type: text/markdown
26
25
 
@@ -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, LatentSpaceReduction, CustomLatentSpaceComputedAt
10
10
  from code_loader.contract.visualizer_classes import LeapImage, LeapText, LeapGraph, LeapHorizontalBar, \
11
- LeapTextMask, LeapImageMask, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapAudio
11
+ LeapTextMask, LeapImageMask, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapAudio, LeapVolume
12
12
  from code_loader.contract.sim_config import SimConfig
13
13
 
14
14
  custom_latent_space_attribute = "custom_latent_space"
@@ -213,7 +213,7 @@ VisualizerCallableInterface = Union[
213
213
  ]
214
214
 
215
215
  LeapData = Union[LeapImage, LeapText, LeapGraph, LeapHorizontalBar, LeapImageMask, LeapTextMask, LeapImageWithBBox,
216
- LeapImageWithHeatmap, LeapVideo, LeapAudio]
216
+ LeapImageWithHeatmap, LeapVideo, LeapAudio, LeapVolume]
217
217
 
218
218
  CustomCallableInterface = Callable[..., Any]
219
219
 
@@ -29,6 +29,7 @@ class LeapDataType(Enum):
29
29
  ImageWithHeatmap = 'ImageWithHeatmap'
30
30
  Video = 'Video'
31
31
  Audio = 'Audio'
32
+ Volume = 'Volume'
32
33
 
33
34
 
34
35
  class MetricDirection(Enum):
@@ -377,6 +377,60 @@ class LeapAudio:
377
377
  raise LeapValidationError(f'sample_rate must be a positive int, got {self.sample_rate}')
378
378
 
379
379
 
380
+ @dataclass
381
+ class LeapVolume:
382
+ """
383
+ Visualizer representing a volume (3-D scalar field) for Tensorleap: a CT/MRI scan, a microscopy
384
+ z-stack, or any (D, H, W) grid. The UI slices it along any axis; the engine stores it as uint8.
385
+
386
+ Attributes:
387
+ data (npt.NDArray[np.float32] | npt.NDArray[np.uint8]): The volume, shaped [D, H, W]. Any finite value
388
+ range is accepted: the engine min-max quantizes it to uint8 for display and keeps the original
389
+ [min, max] so the UI can show true values.
390
+ mask (Optional[npt.NDArray[np.uint8]]): Optional label volume shaped like `data`; voxel value v is the
391
+ index into `labels`.
392
+ labels (Optional[List[str]]): Names for mask values; required with `mask`, len(labels) > mask.max().
393
+ spacing (Optional[Tuple[float, float, float]]): Physical voxel size per axis (D, H, W), e.g. mm, so the
394
+ UI renders anisotropic voxels with the right aspect. None means (1, 1, 1).
395
+ type (LeapDataType): The data type, default is LeapDataType.Volume.
396
+
397
+ Example:
398
+ volume = np.random.rand(64, 128, 128).astype(np.float32)
399
+ mask = (volume > 0.8).astype(np.uint8)
400
+ leap_volume = LeapVolume(data=volume, mask=mask, labels=["background", "lesion"], spacing=(2.0, 0.7, 0.7))
401
+ """
402
+ data: Union[npt.NDArray[np.float32], npt.NDArray[np.uint8]]
403
+ mask: Optional[npt.NDArray[np.uint8]] = None
404
+ labels: Optional[List[str]] = None
405
+ spacing: Optional[Tuple[float, float, float]] = None
406
+ type: LeapDataType = LeapDataType.Volume
407
+
408
+ def __post_init__(self) -> None:
409
+ validate_type(self.type, LeapDataType.Volume)
410
+ validate_type(type(self.data), np.ndarray)
411
+ validate_type(self.data.dtype, [np.uint8, np.float32])
412
+ validate_type(len(self.data.shape), 3, 'Volume data must be of shape 3 [D, H, W]')
413
+ if not np.isfinite(self.data).all():
414
+ raise LeapValidationError('Volume data must be finite (no NaN/Inf)')
415
+ if (self.mask is None) != (self.labels is None):
416
+ raise LeapValidationError('Volume mask and labels must be given together')
417
+ if self.mask is not None:
418
+ validate_type(type(self.mask), np.ndarray)
419
+ validate_type(self.mask.dtype, np.uint8)
420
+ if self.mask.shape != self.data.shape:
421
+ raise LeapValidationError(
422
+ f'Volume mask shape {self.mask.shape} must equal data shape {self.data.shape}')
423
+ validate_type(type(self.labels), list)
424
+ for label in self.labels:
425
+ validate_type(type(label), str)
426
+ if self.mask.size and int(self.mask.max()) >= len(self.labels):
427
+ raise LeapValidationError(
428
+ f'Volume mask has value {int(self.mask.max())} but only {len(self.labels)} labels')
429
+ if self.spacing is not None:
430
+ if len(self.spacing) != 3 or any(float(s) <= 0 for s in self.spacing):
431
+ raise LeapValidationError(f'Volume spacing must be 3 positive numbers, got {self.spacing}')
432
+
433
+
380
434
  map_leap_data_type_to_visualizer_class = {
381
435
  LeapDataType.Image.value: LeapImage,
382
436
  LeapDataType.Graph.value: LeapGraph,
@@ -387,5 +441,6 @@ map_leap_data_type_to_visualizer_class = {
387
441
  LeapDataType.TextMask.value: LeapTextMask,
388
442
  LeapDataType.ImageWithBBox.value: LeapImageWithBBox,
389
443
  LeapDataType.ImageWithHeatmap.value: LeapImageWithHeatmap,
390
- LeapDataType.Audio.value: LeapAudio
444
+ LeapDataType.Audio.value: LeapAudio,
445
+ LeapDataType.Volume.value: LeapVolume,
391
446
  }
@@ -34,7 +34,7 @@ from code_loader.contract.enums import MetricDirection, LeapDataType, DatasetMet
34
34
  from code_loader import leap_binder, LeapLoader
35
35
  from code_loader.contract.mapping import NodeMapping, NodeMappingType, NodeConnection
36
36
  from code_loader.contract.visualizer_classes import LeapImage, LeapImageMask, LeapTextMask, LeapText, LeapGraph, \
37
- LeapHorizontalBar, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapAudio, LeapValidationError, \
37
+ LeapHorizontalBar, LeapImageWithBBox, LeapImageWithHeatmap, LeapVideo, LeapAudio, LeapVolume, LeapValidationError, \
38
38
  map_leap_data_type_to_visualizer_class
39
39
  from code_loader.inner_leap_binder.leapbinder import mapping_runtime_mode_env_var_mame, \
40
40
  _reject_stringized_sample_preprocess_response
@@ -1593,7 +1593,8 @@ def tensorleap_custom_visualizer(name: str, visualizer_type: LeapDataType,
1593
1593
  LeapDataType.ImageWithBBox: LeapImageWithBBox,
1594
1594
  LeapDataType.ImageWithHeatmap: LeapImageWithHeatmap,
1595
1595
  LeapDataType.Video: LeapVideo,
1596
- LeapDataType.Audio: LeapAudio
1596
+ LeapDataType.Audio: LeapAudio,
1597
+ LeapDataType.Volume: LeapVolume,
1597
1598
  }
1598
1599
  validate_output_structure(result, func_name=user_function.__name__,
1599
1600
  expected_type_name=result_type_map[visualizer_type])
@@ -20,7 +20,7 @@ from textwrap import wrap
20
20
  import math
21
21
 
22
22
  from code_loader.contract.visualizer_classes import LeapImage, LeapImageWithBBox, LeapGraph, LeapText, \
23
- LeapHorizontalBar, LeapImageMask, LeapTextMask, LeapImageWithHeatmap, LeapVideo
23
+ LeapHorizontalBar, LeapImageMask, LeapTextMask, LeapImageWithHeatmap, LeapVideo, LeapVolume
24
24
  from code_loader.utils import rescale_min_max
25
25
 
26
26
 
@@ -420,6 +420,28 @@ def plot_video(leap_data: LeapVideo, title: str) -> None:
420
420
  plt.pause(0.1) # Adjust the pause duration as needed
421
421
  plt.show()
422
422
 
423
+ @run_only_on_non_mapping_mode()
424
+ def plot_volume(leap_data: LeapVolume, title: str) -> None:
425
+ """Middle slice along each axis, grayscale as the engine stores it (uint8), mask overlaid."""
426
+ volume = rescale_min_max(leap_data.data.astype(np.float32))
427
+ spacing = leap_data.spacing or (1.0, 1.0, 1.0)
428
+ fig, axes = plt.subplots(1, 3, figsize=(12, 4))
429
+ fig.patch.set_facecolor('black')
430
+ fig.suptitle(title, color='white')
431
+ for axis, ax in enumerate(axes):
432
+ index = volume.shape[axis] // 2
433
+ ax.imshow(np.take(volume, index, axis=axis), cmap='gray', vmin=0, vmax=255)
434
+ if leap_data.mask is not None:
435
+ mask = np.take(leap_data.mask, index, axis=axis)
436
+ ax.imshow(np.ma.masked_where(mask == 0, mask), cmap='tab10', alpha=0.4,
437
+ vmin=0, vmax=max(9, len(leap_data.labels) - 1))
438
+ rows, cols = [i for i in range(3) if i != axis]
439
+ ax.set_aspect(spacing[rows] / spacing[cols])
440
+ ax.set_title(f'axis {axis} / slice {index}', color='white')
441
+ ax.axis('off')
442
+ plt.show()
443
+
444
+
423
445
  @run_only_on_non_mapping_mode()
424
446
  def plot_image_with_heatmap(leap_data: LeapImageWithHeatmap, title: str) -> None:
425
447
  """
@@ -474,4 +496,5 @@ plot_switch = {
474
496
  LeapDataType.ImageWithHeatmap: plot_image_with_heatmap,
475
497
  LeapDataType.ImageWithBBox: plot_image_with_b_box,
476
498
  LeapDataType.Video: plot_video,
499
+ LeapDataType.Volume: plot_volume,
477
500
  }
@@ -5,7 +5,7 @@ import numpy as np
5
5
  import numpy.typing as npt
6
6
 
7
7
  from code_loader.contract.visualizer_classes import LeapImage, LeapGraph, LeapHorizontalBar, LeapText, \
8
- LeapImageMask, LeapTextMask, LeapVideo, LeapAudio
8
+ LeapImageMask, LeapTextMask, LeapVideo, LeapAudio, LeapVolume
9
9
  from code_loader.utils import rescale_min_max
10
10
 
11
11
 
@@ -92,6 +92,20 @@ def default_audio_visualizer(audio: npt.NDArray[np.float32],
92
92
  raise ValueError(f'visual must be 1-D, 2-D, or 3-D, got shape {v.shape}')
93
93
 
94
94
 
95
+ def default_volume_visualizer(data: npt.NDArray[np.float32]) -> LeapVolume:
96
+ """Strip the batch axis and a single channel axis (first or last) to get a [D, H, W] volume."""
97
+ volume = data[0]
98
+ if volume.ndim == 4:
99
+ if volume.shape[0] == 1:
100
+ volume = volume[0]
101
+ elif volume.shape[-1] == 1:
102
+ volume = volume[..., 0]
103
+ if volume.ndim != 3:
104
+ raise ValueError('default_volume_visualizer expects [1, D, H, W], [1, 1, D, H, W] or [1, D, H, W, 1], '
105
+ f'got {data.shape}')
106
+ return LeapVolume(volume)
107
+
108
+
95
109
  def default_graph_visualizer(data: npt.NDArray[np.float32]) -> LeapGraph:
96
110
  return LeapGraph(data[0])
97
111
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "code-loader"
3
- version = "1.0.209"
3
+ version = "1.0.210"
4
4
  description = ""
5
5
  authors = ["dorhar <doron.harnoy@tensorleap.ai>"]
6
6
  license = "MIT"
File without changes
File without changes