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.
- dataeval_flow/__init__.py +93 -0
- dataeval_flow/__main__.py +149 -0
- dataeval_flow/_app/__init__.py +5 -0
- dataeval_flow/_app/_model/__init__.py +5 -0
- dataeval_flow/_app/_model/_coerce.py +126 -0
- dataeval_flow/_app/_model/_discover.py +171 -0
- dataeval_flow/_app/_model/_execution.py +108 -0
- dataeval_flow/_app/_model/_introspect.py +280 -0
- dataeval_flow/_app/_model/_item.py +213 -0
- dataeval_flow/_app/_model/_registry.py +255 -0
- dataeval_flow/_app/_model/_state.py +322 -0
- dataeval_flow/_app/_model/_undo.py +61 -0
- dataeval_flow/_app/_panes/__init__.py +35 -0
- dataeval_flow/_app/_panes/_config_pane.py +173 -0
- dataeval_flow/_app/_panes/_result_pane.py +125 -0
- dataeval_flow/_app/_panes/_task_pane.py +91 -0
- dataeval_flow/_app/_panes/_widgets.py +111 -0
- dataeval_flow/_app/_screens/__init__.py +25 -0
- dataeval_flow/_app/_screens/_base.py +242 -0
- dataeval_flow/_app/_screens/_detail.py +333 -0
- dataeval_flow/_app/_screens/_model.py +102 -0
- dataeval_flow/_app/_screens/_params.py +80 -0
- dataeval_flow/_app/_screens/_pathpicker.py +68 -0
- dataeval_flow/_app/_screens/_section.py +621 -0
- dataeval_flow/_app/_screens/_settings.py +183 -0
- dataeval_flow/_app/_viewmodel/__init__.py +15 -0
- dataeval_flow/_app/_viewmodel/_builder_vm.py +272 -0
- dataeval_flow/_app/_viewmodel/_model_vm.py +70 -0
- dataeval_flow/_app/_viewmodel/_rendering.py +189 -0
- dataeval_flow/_app/_viewmodel/_result_vm.py +210 -0
- dataeval_flow/_app/_viewmodel/_section_vm.py +224 -0
- dataeval_flow/_app/app.py +742 -0
- dataeval_flow/_app/cli.py +592 -0
- dataeval_flow/_logging.py +102 -0
- dataeval_flow/cache.py +1355 -0
- dataeval_flow/config/__init__.py +80 -0
- dataeval_flow/config/_loader.py +79 -0
- dataeval_flow/config/_merge.py +92 -0
- dataeval_flow/config/_models.py +115 -0
- dataeval_flow/config/_paths.py +85 -0
- dataeval_flow/config/schemas/__init__.py +112 -0
- dataeval_flow/config/schemas/_dataset.py +111 -0
- dataeval_flow/config/schemas/_extractor.py +119 -0
- dataeval_flow/config/schemas/_metadata.py +28 -0
- dataeval_flow/config/schemas/_preprocessor.py +18 -0
- dataeval_flow/config/schemas/_selection.py +100 -0
- dataeval_flow/config/schemas/_task.py +89 -0
- dataeval_flow/config/schemas/_workflow.py +135 -0
- dataeval_flow/dataset.py +635 -0
- dataeval_flow/embeddings.py +135 -0
- dataeval_flow/metadata.py +48 -0
- dataeval_flow/preprocessing.py +141 -0
- dataeval_flow/py.typed +0 -0
- dataeval_flow/runner.py +118 -0
- dataeval_flow/selection.py +50 -0
- dataeval_flow/workflow/__init__.py +328 -0
- dataeval_flow/workflow/_text_report.py +511 -0
- dataeval_flow/workflow/base.py +69 -0
- dataeval_flow/workflow/orchestrator.py +454 -0
- dataeval_flow/workflows/__init__.py +1 -0
- dataeval_flow/workflows/analysis/__init__.py +38 -0
- dataeval_flow/workflows/analysis/outputs.py +202 -0
- dataeval_flow/workflows/analysis/params.py +114 -0
- dataeval_flow/workflows/analysis/workflow.py +1313 -0
- dataeval_flow/workflows/cleaning/__init__.py +23 -0
- dataeval_flow/workflows/cleaning/outputs.py +200 -0
- dataeval_flow/workflows/cleaning/params.py +160 -0
- dataeval_flow/workflows/cleaning/report.py +304 -0
- dataeval_flow/workflows/cleaning/workflow.py +794 -0
- dataeval_flow/workflows/drift/__init__.py +1 -0
- dataeval_flow/workflows/drift/outputs.py +144 -0
- dataeval_flow/workflows/drift/params.py +332 -0
- dataeval_flow/workflows/drift/report.py +201 -0
- dataeval_flow/workflows/drift/workflow.py +647 -0
- dataeval_flow/workflows/ood/__init__.py +1 -0
- dataeval_flow/workflows/ood/outputs.py +134 -0
- dataeval_flow/workflows/ood/params.py +161 -0
- dataeval_flow/workflows/ood/report.py +311 -0
- dataeval_flow/workflows/ood/workflow.py +728 -0
- dataeval_flow/workflows/prioritization/__init__.py +1 -0
- dataeval_flow/workflows/prioritization/outputs.py +122 -0
- dataeval_flow/workflows/prioritization/params.py +124 -0
- dataeval_flow/workflows/prioritization/report.py +117 -0
- dataeval_flow/workflows/prioritization/workflow.py +587 -0
- dataeval_flow/workflows/splitting/__init__.py +25 -0
- dataeval_flow/workflows/splitting/outputs.py +101 -0
- dataeval_flow/workflows/splitting/params.py +61 -0
- dataeval_flow/workflows/splitting/report.py +485 -0
- dataeval_flow/workflows/splitting/workflow.py +371 -0
- dataeval_flow-0.1.0.dist-info/METADATA +305 -0
- dataeval_flow-0.1.0.dist-info/RECORD +94 -0
- dataeval_flow-0.1.0.dist-info/WHEEL +4 -0
- dataeval_flow-0.1.0.dist-info/entry_points.txt +2 -0
- 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
|
dataeval_flow/runner.py
ADDED
|
@@ -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)
|