evaluators 2.0.0__tar.gz → 2.1.1__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 (52) hide show
  1. {evaluators-2.0.0 → evaluators-2.1.1}/PKG-INFO +4 -1
  2. {evaluators-2.0.0 → evaluators-2.1.1}/pyproject.toml +5 -1
  3. evaluators-2.0.0/sources/evaluators/integrations/pl.py → evaluators-2.1.1/sources/evaluators/integrations/lightning.py +19 -21
  4. evaluators-2.1.1/sources/evaluators/memory/utils/encoding.py +148 -0
  5. evaluators-2.0.0/sources/evaluators/memory/utils/encoding.py +0 -102
  6. {evaluators-2.0.0 → evaluators-2.1.1}/README.md +0 -0
  7. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/__init__.py +0 -0
  8. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/_engine.py +0 -0
  9. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/_streams.py +0 -0
  10. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/cli/__main__.py +0 -0
  11. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/cli/memory.py +0 -0
  12. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/constants.py +0 -0
  13. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/__init__.py +0 -0
  14. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/encoded_memmap/__init__.py +0 -0
  15. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/encoded_memmap/_reader.py +0 -0
  16. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/encoded_memmap/_writer.py +0 -0
  17. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/interfaces.py +0 -0
  18. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/lmdb/__init__.py +0 -0
  19. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/lmdb/_reader.py +0 -0
  20. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/lmdb/_writer.py +0 -0
  21. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/memmap/__init__.py +0 -0
  22. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/memmap/_buffers.py +0 -0
  23. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/memmap/_reader.py +0 -0
  24. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/memmap/_writer.py +0 -0
  25. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/memory/schema.py +0 -0
  26. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/__init__.py +0 -0
  27. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/adapters/coco.py +0 -0
  28. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/adapters/trackeval.py +0 -0
  29. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/domain/depth.py +0 -0
  30. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/domain/dvps.py +0 -0
  31. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/domain/segmentation/__init__.py +0 -0
  32. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/domain/segmentation/_utils.py +0 -0
  33. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/domain/segmentation/panoptic.py +0 -0
  34. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/domain/segmentation/semantic.py +0 -0
  35. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/domain/step.py +0 -0
  36. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/domain/tracking.py +0 -0
  37. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/metrics/stats.py +0 -0
  38. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/presets/__init__.py +0 -0
  39. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/presets/cityscapes.py +0 -0
  40. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/presets/kitti.py +0 -0
  41. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/presets/semkitti.py +0 -0
  42. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/scheduling/__init__.py +0 -0
  43. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/scheduling/_catalog.py +0 -0
  44. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/scheduling/_partitioner.py +0 -0
  45. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/types.py +0 -0
  46. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/utils/comms.py +0 -0
  47. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/utils/tensor_ops.py +0 -0
  48. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/visuals/__init__.py +0 -0
  49. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/visuals/metric.py +0 -0
  50. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/visuals/tools/__init__.py +0 -0
  51. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/visuals/tools/_visualizer.py +0 -0
  52. {evaluators-2.0.0 → evaluators-2.1.1}/sources/evaluators/visuals/tools/transform.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: evaluators
3
- Version: 2.0.0
3
+ Version: 2.1.1
4
4
  Summary: Various scene understanding and perception evaluation metrics.
5
5
  Author: Kurt Stolle
6
6
  Author-email: Kurt Stolle <kurt@computer.org>
@@ -27,7 +27,10 @@ Requires-Dist: lmdb
27
27
  Requires-Dist: tifffile
28
28
  Requires-Dist: pydantic
29
29
  Requires-Dist: opencv-python>=4.13.0.92
30
+ Requires-Dist: imagecodecs>=2026.3.6
31
+ Requires-Dist: lightning>=2.6.1 ; extra == 'lightning'
30
32
  Requires-Python: >=3.13, <3.14
33
+ Provides-Extra: lightning
31
34
  Description-Content-Type: text/markdown
32
35
 
33
36
  # Scalable Distributed Evaluation for Computer Vision
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "evaluators"
7
- version = "2.0.0"
7
+ version = "2.1.1"
8
8
  description = "Various scene understanding and perception evaluation metrics."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13, <3.14"
@@ -35,8 +35,12 @@ dependencies = [
35
35
  "tifffile",
36
36
  "pydantic",
37
37
  "opencv-python>=4.13.0.92",
38
+ "imagecodecs>=2026.3.6",
38
39
  ]
39
40
 
41
+ [project.optional-dependencies]
42
+ lightning = ["lightning>=2.6.1"]
43
+
40
44
  [dependency-groups]
41
45
  dev = [
42
46
  "hypothesis>=6.140.2",
@@ -13,12 +13,9 @@ Exports
13
13
  import logging
14
14
  import shutil
15
15
  from pathlib import Path
16
- from typing import Type
17
16
 
18
17
  from evaluators import MetricStream, run_offline_evaluation
19
18
  from evaluators.memory import (
20
- EncodedMemmapTemporalWriter,
21
- LMDBTemporalWriter,
22
19
  MemmapTemporalWriter,
23
20
  TemporalWriterProtocol,
24
21
  )
@@ -39,34 +36,33 @@ class _DistributedWriterBase(Callback):
39
36
  self,
40
37
  output_dir: str | Path,
41
38
  *,
42
- chunk_size_frames: int = 1_000,
43
- backend: Type[TemporalWriterProtocol] = MemmapTemporalWriter,
39
+ backend: type[TemporalWriterProtocol] = MemmapTemporalWriter,
44
40
  **backend_kwargs,
45
41
  ) -> None:
46
42
  super().__init__()
47
43
  self.output_dir = Path(output_dir)
48
- self.chunk_size_frames = chunk_size_frames
49
44
  self.backend = backend
50
45
  self.backend_kwargs = backend_kwargs
51
- # Map: stream_name -> (MetricStream, TemporalWriterProtocol)
52
- self._active_streams: dict[str, tuple[MetricStream, TemporalWriterProtocol]] = {}
46
+ # Map: stream_name -> (MetricStream, Backend)
47
+ self._active_streams: dict[
48
+ str, tuple[MetricStream, TemporalWriterProtocol]
49
+ ] = {}
53
50
 
54
51
  def _setup_streams(self, pl_module: LightningModule) -> None:
55
52
  """Discovers MetricStreams and binds them to dedicated writers."""
56
53
  self._active_streams = {}
57
54
 
58
- discovered_streams: list[MetricStream] = []
59
-
60
55
  # Robust Discovery: Walk the module tree using standard PyTorch traversal
61
- for module in pl_module.modules():
62
- if isinstance(module, MetricStream):
63
- discovered_streams.append(module)
56
+ discovered_streams: list[MetricStream] = [
57
+ module for module in pl_module.modules() if isinstance(module, MetricStream)
58
+ ]
64
59
 
65
60
  if not discovered_streams:
66
61
  if comms.get_rank() == 0:
67
62
  logger.warning(
68
- f"No MetricStreams found in {pl_module.__class__.__name__}. "
69
- f"{self.__class__.__name__} will be idle."
63
+ "No MetricStreams found in %s; %s will be idle.",
64
+ pl_module.__class__.__name__,
65
+ self.__class__.__name__,
70
66
  )
71
67
  return
72
68
 
@@ -75,22 +71,22 @@ class _DistributedWriterBase(Callback):
75
71
  stream_dir = self.output_dir / stream.name
76
72
 
77
73
  if stream.schema is None:
78
- raise RuntimeError(
74
+ msg = (
79
75
  f"MetricStream '{stream.name}' has no schema defined. "
80
76
  "Explicit schemas are required for storage backends."
81
77
  )
78
+ raise RuntimeError(msg)
82
79
 
83
80
  writer = self.backend(
84
81
  output_dir=str(stream_dir),
85
82
  schema=stream.schema,
86
- chunk_size_frames=self.chunk_size_frames,
87
83
  **self.backend_kwargs,
88
84
  )
89
85
  stream.bind(writer)
90
86
  self._active_streams[stream.name] = (stream, writer)
91
87
 
92
88
  def _close_writers(self) -> None:
93
- """Closes all writers and clears the active streams list."""
89
+ """Close all writers and clears the active streams list."""
94
90
  for name, (_, writer) in self._active_streams.items():
95
91
  writer.close()
96
92
  logger.debug("Closed writer for stream: %s", name)
@@ -129,7 +125,7 @@ class DistributedEvaluationCallback(_DistributedWriterBase):
129
125
  output_dir: str | Path,
130
126
  *,
131
127
  chunk_size_frames: int = 1_000,
132
- backend: Type[TemporalWriterProtocol] = MemmapTemporalWriter,
128
+ backend: type[TemporalWriterProtocol] = MemmapTemporalWriter,
133
129
  keep_artifacts: bool = True,
134
130
  **backend_kwargs,
135
131
  ) -> None:
@@ -180,7 +176,7 @@ class DistributedEvaluationCallback(_DistributedWriterBase):
180
176
  comms.barrier()
181
177
  if not self.keep_artifacts and comms.get_rank() == 0:
182
178
  shutil.rmtree(self.output_dir)
183
-
179
+
184
180
  self._active_streams = {}
185
181
 
186
182
 
@@ -193,6 +189,8 @@ class DistributedInferenceWriter(_DistributedWriterBase):
193
189
  def on_predict_start(self, trainer: Trainer, pl_module: LightningModule) -> None:
194
190
  self._setup_streams(pl_module)
195
191
 
196
- def on_predict_epoch_end(self, trainer: Trainer, pl_module: LightningModule) -> None:
192
+ def on_predict_epoch_end(
193
+ self, trainer: Trainer, pl_module: LightningModule
194
+ ) -> None:
197
195
  self._close_writers()
198
196
  self._active_streams = {}
@@ -0,0 +1,148 @@
1
+ """
2
+ Serialization Layer for LMDB Backend.
3
+ =====================================
4
+
5
+ Provides functional registries to convert PyTorch tensors to bytes (and back)
6
+ using image-specific codecs (PNG, TIFF) and generic raw formats.
7
+ """
8
+
9
+ import functools
10
+
11
+ import io
12
+ from typing import Callable, Literal
13
+
14
+ import numpy as np
15
+ import tifffile
16
+ import torch
17
+ import torchvision.io
18
+
19
+ __all__ = [
20
+ "decode_png",
21
+ "decode_raw",
22
+ "decode_tiff",
23
+ "encode_png",
24
+ "encode_raw",
25
+ "encode_tiff",
26
+ ]
27
+
28
+
29
+ def encode_png(tensor: torch.Tensor) -> bytes:
30
+ """
31
+ Encode a tensor to PNG.
32
+
33
+ Expects (C, H, W) shape and uint8 dtype.
34
+ """
35
+ if tensor.ndim != 3:
36
+ msg = (
37
+ f"Expected 3D tensor (C, H, W) for PNG encoding, got shape: {tensor.shape}"
38
+ )
39
+ raise ValueError(msg)
40
+ if tensor.dtype != torch.uint8:
41
+ msg = f"Expected uint8 dtype for PNG encoding, got: {tensor.dtype}"
42
+ raise ValueError(msg)
43
+
44
+ encoded = torchvision.io.encode_png(tensor.cpu())
45
+ # encode_png returns a 1D uint8 tensor. Get bytes.
46
+ return encoded.numpy().tobytes()
47
+
48
+
49
+ def decode_png(data: bytes, device: torch.device | None = None) -> torch.Tensor:
50
+ """
51
+ Decode PNG bytes back to a tensor.
52
+ """
53
+ # Try decode_image which auto-detects
54
+ t_1d = torch.frombuffer(bytearray(data), dtype=torch.uint8)
55
+ tensor = torchvision.io.decode_image(
56
+ t_1d, mode=torchvision.io.ImageReadMode.UNCHANGED
57
+ )
58
+ if device is not None:
59
+ tensor = tensor.to(device)
60
+ return tensor
61
+
62
+
63
+ def encode_tiff(tensor: torch.Tensor, **kwargs) -> bytes:
64
+ """
65
+ Encode a tensor to TIFF format using ``tifffile``.
66
+
67
+ Suitable for depth maps.
68
+ """
69
+ if tensor.ndim != 3:
70
+ msg = (
71
+ f"Expected 3D tensor (C, H, W) for TIFF encoding, got shape: {tensor.shape}"
72
+ )
73
+ raise ValueError(msg)
74
+
75
+ # Determine `imwrite` parameters based on dtype
76
+ if len(kwargs) > 0:
77
+ write = functools.partial(tifffile.imwrite, **kwargs)
78
+ elif tensor.is_floating_point():
79
+ # Assume depth maps, use LERC compression with a small error threshold of 1mm
80
+ write = functools.partial(
81
+ tifffile.imwrite,
82
+ compression="lerc",
83
+ compressionargs={"level": 0.001},
84
+ )
85
+ else:
86
+ # Assume segmentation masks
87
+ write = functools.partial(
88
+ tifffile.imwrite, compression="zstd", compressionargs={"level": 3}
89
+ )
90
+
91
+ # Permute to (H, W, C), remove channel if C=1, then move to CPU and convert to numpy
92
+ arr = tensor.permute(1, 2, 0).squeeze(-1).cpu().numpy()
93
+
94
+ # Ensure C-order
95
+ arr = np.ascontiguousarray(arr)
96
+
97
+ # Write to an in-memory buffer and get bytes
98
+ with io.BytesIO() as buf:
99
+ write(buf, arr, **kwargs)
100
+ return buf.getvalue()
101
+
102
+
103
+ def decode_tiff(data: bytes, device: torch.device | None = None) -> torch.Tensor:
104
+ """
105
+ Decode TIFF bytes back to a tensor.
106
+
107
+ Returns a tensor in (C, H, W) format.
108
+ """
109
+ with io.BytesIO(data) as buf:
110
+ arr = tifffile.imread(buf)
111
+
112
+ # tifffile returns numpy array, convert to tensor
113
+ tensor = torch.from_numpy(arr)
114
+
115
+ # Convert (H, W) to (H, W, 1) if single-channel
116
+ if tensor.ndim == 2:
117
+ tensor.unsqueeze_(-1)
118
+
119
+ # Permute back to (C, H, W)
120
+ tensor = tensor.permute(2, 0, 1)
121
+
122
+ # Move to device if specified
123
+ if device is not None:
124
+ tensor = tensor.to(device)
125
+
126
+ # Ensure contiguous memory for better performance in downstream processing
127
+ return tensor.contiguous()
128
+
129
+
130
+ def encode_raw(tensor: torch.Tensor) -> bytes:
131
+ """
132
+ Generic raw serialization using PyTorch's native save (uses zip/pickle).
133
+ Good for small metadata like sequence_ids or unsupported large tensors.
134
+ """
135
+ with io.BytesIO() as buf:
136
+ torch.save(tensor.cpu(), buf)
137
+ return buf.getvalue()
138
+
139
+
140
+ def decode_raw(data: bytes, device: torch.device | None = None) -> torch.Tensor:
141
+ """
142
+ Decode generic raw bytes.
143
+ """
144
+ with io.BytesIO(data) as buf:
145
+ tensor = torch.load(buf, weights_only=True)
146
+ if device is not None:
147
+ tensor = tensor.to(device)
148
+ return tensor
@@ -1,102 +0,0 @@
1
- """
2
- Serialization Layer for LMDB Backend.
3
- =====================================
4
-
5
- Provides functional registries to convert PyTorch tensors to bytes (and back)
6
- using image-specific codecs (PNG, TIFF) and generic raw formats.
7
- """
8
-
9
- import io
10
- from typing import Callable, Literal
11
-
12
- import numpy as np
13
- import tifffile
14
- import torch
15
- import torchvision.io
16
-
17
- __all__ = [
18
- "encode_png",
19
- "decode_png",
20
- "encode_tiff",
21
- "decode_tiff",
22
- "encode_raw",
23
- "decode_raw",
24
- ]
25
-
26
-
27
- def encode_png(tensor: torch.Tensor) -> bytes:
28
- """
29
- Encodes a tensor to PNG.
30
- Expects (C, H, W) or (H, W). If uint8 is not the dtype, it casts (assumes categorical mask).
31
- """
32
- if tensor.ndim == 2:
33
- tensor = tensor.unsqueeze(0)
34
- if tensor.dtype != torch.uint8:
35
- # Warning: For segmentation masks, values > 255 will be clipped if not handled carefully,
36
- # but typically panoptic ids or labels are packed, or we only support uint8 for png.
37
- # Actually, PNG can support 16-bit. torchvision's encode_png only supports uint8.
38
- # If >255, we should probably fall back to raw or use a different encoder.
39
- if tensor.max() > 255:
40
- return encode_raw(tensor)
41
- tensor = tensor.to(torch.uint8)
42
-
43
- encoded = torchvision.io.encode_png(tensor.cpu())
44
- # encode_png returns a 1D uint8 tensor. Get bytes.
45
- return encoded.numpy().tobytes()
46
-
47
-
48
- def decode_png(data: bytes, device: torch.device | None = None) -> torch.Tensor:
49
- """
50
- Decodes PNG bytes back to a tensor.
51
- """
52
- # Try decode_image which auto-detects
53
- t_1d = torch.frombuffer(bytearray(data), dtype=torch.uint8)
54
- tensor = torchvision.io.decode_image(t_1d, mode=torchvision.io.ImageReadMode.UNCHANGED)
55
- if device is not None:
56
- tensor = tensor.to(device)
57
- return tensor
58
-
59
-
60
- def encode_tiff(tensor: torch.Tensor) -> bytes:
61
- """
62
- Encodes a tensor to TIFF format using tifffile. Suitable for depth maps.
63
- """
64
- arr = tensor.cpu().numpy()
65
- with io.BytesIO() as buf:
66
- tifffile.imwrite(buf, arr)
67
- return buf.getvalue()
68
-
69
-
70
- def decode_tiff(data: bytes, device: torch.device | None = None) -> torch.Tensor:
71
- """
72
- Decodes TIFF bytes back to a tensor.
73
- """
74
- with io.BytesIO(data) as buf:
75
- arr = tifffile.imread(buf)
76
-
77
- # tifffile returns numpy array, convert to tensor
78
- tensor = torch.from_numpy(arr)
79
- if device is not None:
80
- tensor = tensor.to(device)
81
- return tensor
82
-
83
-
84
- def encode_raw(tensor: torch.Tensor) -> bytes:
85
- """
86
- Generic raw serialization using PyTorch's native save (uses zip/pickle).
87
- Good for small metadata like sequence_ids or unsupported large tensors.
88
- """
89
- with io.BytesIO() as buf:
90
- torch.save(tensor.cpu(), buf)
91
- return buf.getvalue()
92
-
93
-
94
- def decode_raw(data: bytes, device: torch.device | None = None) -> torch.Tensor:
95
- """
96
- Decodes generic raw bytes.
97
- """
98
- with io.BytesIO(data) as buf:
99
- tensor = torch.load(buf, weights_only=True)
100
- if device is not None:
101
- tensor = tensor.to(device)
102
- return tensor
File without changes