dataeval-flow 0.1.0__py3-none-any.whl

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 (94) hide show
  1. dataeval_flow/__init__.py +93 -0
  2. dataeval_flow/__main__.py +149 -0
  3. dataeval_flow/_app/__init__.py +5 -0
  4. dataeval_flow/_app/_model/__init__.py +5 -0
  5. dataeval_flow/_app/_model/_coerce.py +126 -0
  6. dataeval_flow/_app/_model/_discover.py +171 -0
  7. dataeval_flow/_app/_model/_execution.py +108 -0
  8. dataeval_flow/_app/_model/_introspect.py +280 -0
  9. dataeval_flow/_app/_model/_item.py +213 -0
  10. dataeval_flow/_app/_model/_registry.py +255 -0
  11. dataeval_flow/_app/_model/_state.py +322 -0
  12. dataeval_flow/_app/_model/_undo.py +61 -0
  13. dataeval_flow/_app/_panes/__init__.py +35 -0
  14. dataeval_flow/_app/_panes/_config_pane.py +173 -0
  15. dataeval_flow/_app/_panes/_result_pane.py +125 -0
  16. dataeval_flow/_app/_panes/_task_pane.py +91 -0
  17. dataeval_flow/_app/_panes/_widgets.py +111 -0
  18. dataeval_flow/_app/_screens/__init__.py +25 -0
  19. dataeval_flow/_app/_screens/_base.py +242 -0
  20. dataeval_flow/_app/_screens/_detail.py +333 -0
  21. dataeval_flow/_app/_screens/_model.py +102 -0
  22. dataeval_flow/_app/_screens/_params.py +80 -0
  23. dataeval_flow/_app/_screens/_pathpicker.py +68 -0
  24. dataeval_flow/_app/_screens/_section.py +621 -0
  25. dataeval_flow/_app/_screens/_settings.py +183 -0
  26. dataeval_flow/_app/_viewmodel/__init__.py +15 -0
  27. dataeval_flow/_app/_viewmodel/_builder_vm.py +272 -0
  28. dataeval_flow/_app/_viewmodel/_model_vm.py +70 -0
  29. dataeval_flow/_app/_viewmodel/_rendering.py +189 -0
  30. dataeval_flow/_app/_viewmodel/_result_vm.py +210 -0
  31. dataeval_flow/_app/_viewmodel/_section_vm.py +224 -0
  32. dataeval_flow/_app/app.py +742 -0
  33. dataeval_flow/_app/cli.py +592 -0
  34. dataeval_flow/_logging.py +102 -0
  35. dataeval_flow/cache.py +1355 -0
  36. dataeval_flow/config/__init__.py +80 -0
  37. dataeval_flow/config/_loader.py +79 -0
  38. dataeval_flow/config/_merge.py +92 -0
  39. dataeval_flow/config/_models.py +115 -0
  40. dataeval_flow/config/_paths.py +85 -0
  41. dataeval_flow/config/schemas/__init__.py +112 -0
  42. dataeval_flow/config/schemas/_dataset.py +111 -0
  43. dataeval_flow/config/schemas/_extractor.py +119 -0
  44. dataeval_flow/config/schemas/_metadata.py +28 -0
  45. dataeval_flow/config/schemas/_preprocessor.py +18 -0
  46. dataeval_flow/config/schemas/_selection.py +100 -0
  47. dataeval_flow/config/schemas/_task.py +89 -0
  48. dataeval_flow/config/schemas/_workflow.py +135 -0
  49. dataeval_flow/dataset.py +635 -0
  50. dataeval_flow/embeddings.py +135 -0
  51. dataeval_flow/metadata.py +48 -0
  52. dataeval_flow/preprocessing.py +141 -0
  53. dataeval_flow/py.typed +0 -0
  54. dataeval_flow/runner.py +118 -0
  55. dataeval_flow/selection.py +50 -0
  56. dataeval_flow/workflow/__init__.py +328 -0
  57. dataeval_flow/workflow/_text_report.py +511 -0
  58. dataeval_flow/workflow/base.py +69 -0
  59. dataeval_flow/workflow/orchestrator.py +454 -0
  60. dataeval_flow/workflows/__init__.py +1 -0
  61. dataeval_flow/workflows/analysis/__init__.py +38 -0
  62. dataeval_flow/workflows/analysis/outputs.py +202 -0
  63. dataeval_flow/workflows/analysis/params.py +114 -0
  64. dataeval_flow/workflows/analysis/workflow.py +1313 -0
  65. dataeval_flow/workflows/cleaning/__init__.py +23 -0
  66. dataeval_flow/workflows/cleaning/outputs.py +200 -0
  67. dataeval_flow/workflows/cleaning/params.py +160 -0
  68. dataeval_flow/workflows/cleaning/report.py +304 -0
  69. dataeval_flow/workflows/cleaning/workflow.py +794 -0
  70. dataeval_flow/workflows/drift/__init__.py +1 -0
  71. dataeval_flow/workflows/drift/outputs.py +144 -0
  72. dataeval_flow/workflows/drift/params.py +332 -0
  73. dataeval_flow/workflows/drift/report.py +201 -0
  74. dataeval_flow/workflows/drift/workflow.py +647 -0
  75. dataeval_flow/workflows/ood/__init__.py +1 -0
  76. dataeval_flow/workflows/ood/outputs.py +134 -0
  77. dataeval_flow/workflows/ood/params.py +161 -0
  78. dataeval_flow/workflows/ood/report.py +311 -0
  79. dataeval_flow/workflows/ood/workflow.py +728 -0
  80. dataeval_flow/workflows/prioritization/__init__.py +1 -0
  81. dataeval_flow/workflows/prioritization/outputs.py +122 -0
  82. dataeval_flow/workflows/prioritization/params.py +124 -0
  83. dataeval_flow/workflows/prioritization/report.py +117 -0
  84. dataeval_flow/workflows/prioritization/workflow.py +587 -0
  85. dataeval_flow/workflows/splitting/__init__.py +25 -0
  86. dataeval_flow/workflows/splitting/outputs.py +101 -0
  87. dataeval_flow/workflows/splitting/params.py +61 -0
  88. dataeval_flow/workflows/splitting/report.py +485 -0
  89. dataeval_flow/workflows/splitting/workflow.py +371 -0
  90. dataeval_flow-0.1.0.dist-info/METADATA +305 -0
  91. dataeval_flow-0.1.0.dist-info/RECORD +94 -0
  92. dataeval_flow-0.1.0.dist-info/WHEEL +4 -0
  93. dataeval_flow-0.1.0.dist-info/entry_points.txt +2 -0
  94. dataeval_flow-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,135 @@
1
+ """Embeddings convenience builder wrapping DataEval."""
2
+
3
+ __all__ = [
4
+ "build_embeddings",
5
+ "build_extractor",
6
+ ]
7
+
8
+ import logging
9
+ from collections.abc import Callable
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ from dataeval import Embeddings
13
+ from dataeval.extractors import BoVWExtractor, FlattenExtractor, OnnxExtractor, TorchExtractor
14
+ from dataeval.protocols import AnnotatedDataset
15
+
16
+ logger: logging.Logger = logging.getLogger(__name__)
17
+
18
+ if TYPE_CHECKING:
19
+ from dataeval_flow.config.schemas import ExtractorConfig
20
+
21
+
22
+ def build_embeddings(
23
+ dataset: AnnotatedDataset[Any],
24
+ extractor_config: "ExtractorConfig",
25
+ transforms: Callable | None = None,
26
+ batch_size: int | None = None,
27
+ ) -> Embeddings:
28
+ """Build Embeddings from dataset and extractor config.
29
+
30
+ Creates the appropriate extractor based on the config's model type and
31
+ wraps it in a DataEval Embeddings instance.
32
+
33
+ Parameters
34
+ ----------
35
+ dataset : MaiteDataset
36
+ Input dataset.
37
+ extractor_config : ExtractorConfig
38
+ Extractor configuration with model type and params.
39
+ transforms : Callable | None
40
+ Preprocessing transforms to apply before encoding.
41
+ Only used by extractor types that accept it (onnx, torch, uncertainty).
42
+
43
+ Returns
44
+ -------
45
+ Embeddings
46
+ DataEval Embeddings instance (implements FeatureExtractor).
47
+ """
48
+
49
+ extractor = build_extractor(extractor_config, transforms)
50
+ return Embeddings(dataset, extractor=extractor, batch_size=batch_size)
51
+
52
+
53
+ def build_extractor(extractor_config: "ExtractorConfig", transforms: Callable | None = None) -> Callable:
54
+ """Build a standalone extractor (not wrapped in Embeddings).
55
+
56
+ Used for workflows that need to apply the extractor separately from embedding extraction
57
+ (e.g. to extract metadata features for evaluation).
58
+
59
+ Parameters
60
+ ----------
61
+ extractor_config : ExtractorConfig
62
+ Extractor configuration with model type and params.
63
+ transforms : Callable | None
64
+ Preprocessing transforms to apply before encoding.
65
+ Only used by extractor types that accept it (onnx, torch, uncertainty).
66
+
67
+ Returns
68
+ -------
69
+ Callable
70
+ A callable extractor function that takes a dataset and returns extracted features.
71
+ """
72
+ from dataeval_flow.config.schemas._extractor import (
73
+ BoVWExtractorConfig,
74
+ FlattenExtractorConfig,
75
+ OnnxExtractorConfig,
76
+ TorchExtractorConfig,
77
+ )
78
+
79
+ logger.debug("Building %s extractor", extractor_config.model)
80
+
81
+ if isinstance(extractor_config, OnnxExtractorConfig):
82
+ extractor = OnnxExtractor(
83
+ extractor_config.model_path,
84
+ transforms=transforms,
85
+ output_name=extractor_config.output_name,
86
+ flatten=extractor_config.flatten,
87
+ )
88
+ elif isinstance(extractor_config, BoVWExtractorConfig):
89
+ extractor = BoVWExtractor(vocab_size=extractor_config.vocab_size)
90
+ elif isinstance(extractor_config, FlattenExtractorConfig):
91
+ extractor = FlattenExtractor()
92
+ elif isinstance(extractor_config, TorchExtractorConfig):
93
+ extractor = _build_torch_extractor(extractor_config, transforms)
94
+ else:
95
+ raise ValueError(
96
+ f"Extractor type '{extractor_config.model}' is not yet implemented. "
97
+ f"Currently supported: onnx, bovw, flatten, torch."
98
+ )
99
+ return extractor
100
+
101
+
102
+ def _build_torch_extractor(
103
+ config: Any,
104
+ transforms: Callable | None = None,
105
+ ) -> TorchExtractor:
106
+ """Build a TorchExtractor from config, loading the model from disk.
107
+
108
+ The *transforms* from ``build_preprocessing`` is a numpy→numpy wrapper.
109
+ ``TorchExtractor`` handles tensor conversion internally and expects raw
110
+ torchvision transforms, so we unwrap the ``v2.Compose`` when possible.
111
+ """
112
+ import torch
113
+ from torchvision.transforms import v2
114
+
115
+ # Unwrap the numpy wrapper produced by build_preprocessing to get the
116
+ # raw v2.Compose that TorchExtractor expects (it handles tensor
117
+ # conversion internally via torch.as_tensor).
118
+ torch_transforms: v2.Compose | Callable | None = None
119
+ if transforms is not None:
120
+ inner = getattr(transforms, "__wrapped__", None)
121
+ if isinstance(inner, v2.Compose):
122
+ torch_transforms = inner
123
+ elif isinstance(transforms, v2.Compose):
124
+ torch_transforms = transforms
125
+ else:
126
+ torch_transforms = transforms
127
+
128
+ model = torch.load(config.model_path, map_location="cpu", weights_only=False)
129
+ return TorchExtractor(
130
+ model,
131
+ transforms=torch_transforms,
132
+ device=config.device,
133
+ layer_name=config.layer_name,
134
+ use_output=config.use_output,
135
+ )
@@ -0,0 +1,48 @@
1
+ """Metadata convenience builder wrapping DataEval."""
2
+
3
+ __all__ = ["build_metadata"]
4
+
5
+ from collections.abc import Mapping, Sequence
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from dataeval import Metadata
9
+ from dataeval.protocols import AnnotatedDataset
10
+
11
+ if TYPE_CHECKING:
12
+ from dataeval_flow.config.schemas import AutoBinMethod
13
+
14
+
15
+ def build_metadata(
16
+ dataset: AnnotatedDataset[Any],
17
+ auto_bin_method: "AutoBinMethod | None" = None,
18
+ exclude: Sequence[str] | None = None,
19
+ continuous_factor_bins: Mapping[str, int | Sequence[float]] | None = None,
20
+ ) -> Metadata:
21
+ """Build Metadata from dataset and config.
22
+
23
+ Parameters
24
+ ----------
25
+ dataset : AnnotatedDataset
26
+ Input dataset.
27
+ auto_bin_method : AutoBinMethod | None
28
+ Method for automatic binning of continuous values.
29
+ exclude : list[str] | None
30
+ Metadata columns to exclude.
31
+ continuous_factor_bins : dict[str, int | list[float]] | None
32
+ Number of uniform bins (int) or explicit bin edges (list[float])
33
+ for specific continuous factors.
34
+
35
+ Returns
36
+ -------
37
+ Metadata
38
+ DataEval Metadata instance.
39
+ """
40
+ kwargs = {}
41
+ if auto_bin_method is not None:
42
+ kwargs["auto_bin_method"] = auto_bin_method
43
+ if exclude is not None:
44
+ kwargs["exclude"] = exclude
45
+ if continuous_factor_bins is not None:
46
+ kwargs["continuous_factor_bins"] = continuous_factor_bins
47
+
48
+ return Metadata(dataset, **kwargs)
@@ -0,0 +1,141 @@
1
+ """Preprocessing utilities for image transforms.
2
+
3
+ Provides configuration-driven preprocessing using torchvision.transforms.v2.
4
+ Any v2 transform can be specified by name in YAML config.
5
+
6
+ The returned callable accepts a numpy CHW array, converts to a torch tensor
7
+ for torchvision transforms, then converts back to a numpy CHW array so that
8
+ ONNX extractors receive the format they expect.
9
+
10
+ Example YAML:
11
+ preprocessing:
12
+ - step: Resize
13
+ params: {size: [256, 256], antialias: true}
14
+ - step: Normalize
15
+ params: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225]}
16
+
17
+ Example Python:
18
+ >>> from dataeval_flow.preprocessing import PreprocessingStep, build_preprocessing
19
+ >>> steps = [PreprocessingStep(step="Resize", params={"size": 256})]
20
+ >>> transform = build_preprocessing(steps)
21
+ >>> output = transform(input_array) # numpy CHW -> numpy CHW
22
+ """
23
+
24
+ __all__ = ["PreprocessingStep", "build_preprocessing"]
25
+
26
+
27
+ import logging
28
+ from collections.abc import Sequence
29
+ from typing import Any
30
+
31
+ import numpy as np
32
+ from numpy.typing import NDArray
33
+ from pydantic import BaseModel, Field
34
+
35
+ logger: logging.Logger = logging.getLogger(__name__)
36
+
37
+
38
+ class PreprocessingStep(BaseModel):
39
+ """Single preprocessing step.
40
+
41
+ Pass-through to torchvision.transforms.v2 - any transform name is allowed.
42
+ See: https://pytorch.org/vision/stable/transforms.html
43
+
44
+ Example
45
+ -------
46
+ >>> step = PreprocessingStep(step="Resize", params={"size": 256, "antialias": True})
47
+ >>> step = PreprocessingStep(step="Normalize", params={"mean": [0.485], "std": [0.229]})
48
+ """
49
+
50
+ step: str = Field(description="Transform name from torchvision.transforms.v2")
51
+ params: dict[str, Any] = Field(default_factory=dict)
52
+
53
+
54
+ class _PreprocessingTransform:
55
+ """Callable wrapper around ``v2.Compose`` with a stable ``repr``.
56
+
57
+ The default ``repr`` of a closure includes the memory address which
58
+ changes every run, causing unnecessary cache misses when the cache
59
+ key is derived from ``repr(transforms)``. This wrapper delegates to
60
+ ``v2.Compose.__repr__`` which is deterministic.
61
+ """
62
+
63
+ __slots__ = ("__wrapped__",)
64
+ __wrapped__: Any
65
+
66
+ def __init__(self, wrapped: Any) -> None:
67
+ self.__wrapped__ = wrapped
68
+
69
+ def __call__(self, image: NDArray[Any]) -> NDArray[Any]:
70
+ import torch
71
+
72
+ tensor = torch.as_tensor(np.ascontiguousarray(image))
73
+ result = self.__wrapped__(tensor)
74
+ return np.asarray(result.detach().cpu())
75
+
76
+ def __repr__(self) -> str:
77
+ return repr(self.__wrapped__)
78
+
79
+
80
+ def build_preprocessing(steps: Sequence[PreprocessingStep]) -> _PreprocessingTransform:
81
+ """Build preprocessing pipeline from config.
82
+
83
+ Builds a torchvision.transforms.v2 pipeline and wraps it so the returned
84
+ callable accepts a **numpy CHW array** and returns a **numpy CHW array**.
85
+ Internally the image is converted to a torch tensor for the v2 transforms,
86
+ then converted back to numpy afterwards.
87
+
88
+ See: https://pytorch.org/vision/stable/transforms.html
89
+
90
+ Parameters
91
+ ----------
92
+ steps : list[PreprocessingStep]
93
+ List of preprocessing steps from config.
94
+
95
+ Returns
96
+ -------
97
+ Callable[[NDArray[Any]], NDArray[Any]]
98
+ Wrapped transform: numpy CHW in, numpy CHW out.
99
+ """
100
+ import torch
101
+ from torchvision.transforms import InterpolationMode, v2
102
+
103
+ # Special parameter converters for non-primitive types
104
+ def _resolve_dtype(name: object) -> torch.dtype:
105
+ attr = str(name)
106
+ result = getattr(torch, attr, None)
107
+ if result is None or not isinstance(result, torch.dtype):
108
+ raise ValueError(f"Unknown torch dtype: '{name}'. Example: 'float32', 'uint8'.")
109
+ return result
110
+
111
+ def _resolve_interpolation(name: object) -> InterpolationMode:
112
+ attr = str(name)
113
+ result = getattr(InterpolationMode, attr, None)
114
+ if result is None:
115
+ raise ValueError(f"Unknown InterpolationMode: '{name}'. Example: 'BILINEAR', 'NEAREST'.")
116
+ return result
117
+
118
+ param_converters = {
119
+ "dtype": _resolve_dtype,
120
+ "interpolation": _resolve_interpolation,
121
+ }
122
+
123
+ logger.debug("Building preprocessing pipeline: %s", [s.step for s in steps])
124
+
125
+ ops: list[Any] = []
126
+ for step in steps:
127
+ params = dict(step.params)
128
+
129
+ # Convert special parameter types
130
+ for key, converter in param_converters.items():
131
+ if key in params:
132
+ params[key] = converter(params[key])
133
+
134
+ # Get transform class and instantiate
135
+ transform_cls = getattr(v2, step.step, None)
136
+ if transform_cls is None:
137
+ raise ValueError(f"Unknown transform: '{step.step}'. Check torchvision.transforms.v2 docs.")
138
+ ops.append(transform_cls(**params))
139
+
140
+ composed = v2.Compose(ops)
141
+ return _PreprocessingTransform(composed)
dataeval_flow/py.typed ADDED
File without changes
@@ -0,0 +1,118 @@
1
+ """Shared CLI/container runner — loads config, runs tasks, writes reports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from dataeval_flow.config._models import PipelineConfig
11
+
12
+ logger: logging.Logger = logging.getLogger(__name__)
13
+
14
+
15
+ def _resolve_config(config_arg: Path | str | None, data_dir: Path) -> PipelineConfig:
16
+ """Resolve and load config from an explicit path or auto-discover from data root."""
17
+ from dataeval_flow.config._loader import load_config, load_config_folder
18
+
19
+ if config_arg is not None:
20
+ config_path = Path(config_arg)
21
+ if not config_path.is_absolute():
22
+ config_path = data_dir / config_path
23
+ else:
24
+ config_path = data_dir
25
+
26
+ if config_path.is_file():
27
+ return load_config(config_path)
28
+ if config_path.is_dir():
29
+ return load_config_folder(config_path)
30
+
31
+ msg = f"Config path not found: {config_path}"
32
+ raise FileNotFoundError(msg)
33
+
34
+
35
+ def run(
36
+ config_arg: Path | str | None,
37
+ output_dir: Path | None = None,
38
+ data_dir: Path | None = None,
39
+ verbosity: int = 0,
40
+ cache_dir: Path | None = None,
41
+ ) -> int:
42
+ """Load config, execute all tasks, and write reports.
43
+
44
+ This is the shared entry point for CLI (``__main__.py``) and container
45
+ (container) usage. For programmatic use, prefer
46
+ :func:`~dataeval_flow.load_config` + :func:`~dataeval_flow.run_tasks`.
47
+
48
+ Parameters
49
+ ----------
50
+ config_arg : Path | str | None
51
+ Path to config file or folder, or None for auto-discovery at data root.
52
+ output_dir : Path | None
53
+ Directory for results, logs, and reports. When ``None``, results
54
+ are printed to the console only — no file artifacts are created.
55
+ data_dir : Path | None
56
+ Root directory for data files. Defaults to ``$DATAEVAL_DATA`` or current directory.
57
+ verbosity : int
58
+ Console verbosity (0=quiet, 1=text report, 2=+INFO, 3=+DEBUG).
59
+ cache_dir : Path | None
60
+ Directory for disk-backed computation cache (embeddings, metadata, stats).
61
+
62
+ Returns
63
+ -------
64
+ int
65
+ 0 if all tasks succeed, 1 if any fail.
66
+ """
67
+ from dataeval_flow._logging import configure_log_levels, flush_logs, setup_logging
68
+ from dataeval_flow.config._loader import get_data_dir
69
+ from dataeval_flow.workflow import run_tasks
70
+
71
+ setup_logging(output_dir, verbosity)
72
+
73
+ resolved_data = get_data_dir(data_dir)
74
+ config = _resolve_config(config_arg, resolved_data)
75
+
76
+ if config.logging:
77
+ configure_log_levels(config.logging.app_level, config.logging.lib_level)
78
+
79
+ if not config.tasks:
80
+ logger.info("No tasks defined in config.")
81
+ return 0
82
+
83
+ results = run_tasks(config, data_dir=resolved_data, cache_dir=cache_dir)
84
+
85
+ failures = 0
86
+ merged: dict[str, dict] = {}
87
+ text_parts: list[str] = []
88
+
89
+ for task, result in zip(config.tasks, results, strict=True):
90
+ if not result.success:
91
+ logger.error(" FAILED: %s", task.name)
92
+ for error in result.errors:
93
+ logger.error(" %s", error)
94
+ failures += 1
95
+ flush_logs()
96
+ continue
97
+
98
+ # --- Text report: summary (no flag) or full detail (-v) ---
99
+ print(result.report(detailed=verbosity >= 1))
100
+
101
+ # --- Collect for file output ---
102
+ merged[task.name] = result.to_dict()
103
+ text_parts.append(result.report(detailed=True))
104
+
105
+ logger.info(" OK: %s", task.name)
106
+ flush_logs()
107
+
108
+ # --- Write file artifacts (only when output_dir is set) ---
109
+ if output_dir is not None and merged:
110
+ import json as json_mod
111
+
112
+ output_dir.mkdir(parents=True, exist_ok=True)
113
+ (output_dir / "result.json").write_text(json_mod.dumps(merged, indent=2), encoding="utf-8")
114
+ (output_dir / "result.txt").write_text("\n".join(text_parts), encoding="utf-8")
115
+ logger.info(" Wrote result.json and result.txt to %s", output_dir)
116
+
117
+ logger.info("Done. %d/%d succeeded.", len(config.tasks) - failures, len(config.tasks))
118
+ return 1 if failures else 0
@@ -0,0 +1,50 @@
1
+ """Selection convenience builder wrapping DataEval."""
2
+
3
+ __all__ = ["build_selection"]
4
+
5
+ from typing import TYPE_CHECKING, TypeVar
6
+
7
+ import dataeval.selection as sel
8
+ from dataeval.protocols import AnnotatedDataset
9
+ from dataeval.selection import Select
10
+
11
+ if TYPE_CHECKING:
12
+ from dataeval_flow.config.schemas import SelectionStep
13
+
14
+ T = TypeVar("T")
15
+
16
+
17
+ def build_selection(dataset: AnnotatedDataset[T], steps: list["SelectionStep"]) -> Select[T]:
18
+ """Build selection pipeline from config.
19
+
20
+ Pass-through to dataeval.selection - no custom logic.
21
+
22
+ Parameters
23
+ ----------
24
+ dataset : MaiteDataset
25
+ Input dataset to wrap with selections.
26
+ steps : list[SelectionStep]
27
+ Selection steps from config.
28
+
29
+ Returns
30
+ -------
31
+ Select
32
+ Dataset wrapped with selection criteria.
33
+
34
+ Example
35
+ -------
36
+ >>> from dataeval_flow.config import SelectionStep
37
+ >>> steps = [
38
+ ... SelectionStep(type="Limit", params={"size": 10000}),
39
+ ... SelectionStep(type="ClassFilter", params={"classes": [0, 1, 2]}),
40
+ ... ]
41
+ >>> filtered = build_selection(dataset, steps)
42
+ """
43
+ selections = []
44
+ for step in steps:
45
+ selection_cls = getattr(sel, step.type, None)
46
+ if selection_cls is None:
47
+ raise ValueError(f"Unknown selection type: '{step.type}'. Check dataeval.selection docs.")
48
+ selections.append(selection_cls(**step.params))
49
+
50
+ return Select(dataset, selections=selections)