dataeval-flow 0.2.2__tar.gz → 0.2.4__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 (113) hide show
  1. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/.gitignore +1 -0
  2. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/PKG-INFO +26 -28
  3. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/README.md +22 -22
  4. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/pyproject.toml +5 -10
  5. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/__main__.py +54 -21
  6. dataeval_flow-0.2.4/src/dataeval_flow/_env.py +74 -0
  7. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_logging.py +32 -15
  8. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_version.py +2 -2
  9. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/coverage/workflow.py +17 -2
  10. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/LICENSE +0 -0
  11. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/__init__.py +0 -0
  12. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/__init__.py +0 -0
  13. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_model/__init__.py +0 -0
  14. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_model/_coerce.py +0 -0
  15. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_model/_discover.py +0 -0
  16. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_model/_execution.py +0 -0
  17. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_model/_introspect.py +0 -0
  18. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_model/_item.py +0 -0
  19. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_model/_registry.py +0 -0
  20. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_model/_state.py +0 -0
  21. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_model/_undo.py +0 -0
  22. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_panes/__init__.py +0 -0
  23. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_panes/_config_pane.py +0 -0
  24. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_panes/_result_pane.py +0 -0
  25. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_panes/_task_pane.py +0 -0
  26. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_panes/_widgets.py +0 -0
  27. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_screens/__init__.py +0 -0
  28. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_screens/_base.py +0 -0
  29. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_screens/_detail.py +0 -0
  30. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_screens/_model.py +0 -0
  31. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_screens/_params.py +0 -0
  32. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_screens/_pathpicker.py +0 -0
  33. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_screens/_section.py +0 -0
  34. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_screens/_settings.py +0 -0
  35. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_viewmodel/__init__.py +0 -0
  36. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_viewmodel/_builder_vm.py +0 -0
  37. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_viewmodel/_model_vm.py +0 -0
  38. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_viewmodel/_rendering.py +0 -0
  39. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_viewmodel/_result_vm.py +0 -0
  40. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/_viewmodel/_section_vm.py +0 -0
  41. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/app.py +0 -0
  42. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_app/cli.py +0 -0
  43. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/_encoding_cli.py +0 -0
  44. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/binning.py +0 -0
  45. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/cache.py +0 -0
  46. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/__init__.py +0 -0
  47. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/_loader.py +0 -0
  48. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/_merge.py +0 -0
  49. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/_models.py +0 -0
  50. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/_paths.py +0 -0
  51. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/schemas/__init__.py +0 -0
  52. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/schemas/_dataset.py +0 -0
  53. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/schemas/_extractor.py +0 -0
  54. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/schemas/_metadata.py +0 -0
  55. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/schemas/_preprocessor.py +0 -0
  56. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/schemas/_task.py +0 -0
  57. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/schemas/_view.py +0 -0
  58. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/config/schemas/_workflow.py +0 -0
  59. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/dataset.py +0 -0
  60. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/embeddings.py +0 -0
  61. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/metadata.py +0 -0
  62. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/policy.py +0 -0
  63. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/preprocessing.py +0 -0
  64. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/preprocessors.py +0 -0
  65. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/py.typed +0 -0
  66. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/runner.py +0 -0
  67. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/view.py +0 -0
  68. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflow/__init__.py +0 -0
  69. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflow/_text_report.py +0 -0
  70. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflow/base.py +0 -0
  71. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflow/orchestrator.py +0 -0
  72. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/__init__.py +0 -0
  73. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/_common.py +0 -0
  74. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/_ontology.py +0 -0
  75. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/analysis/__init__.py +0 -0
  76. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/analysis/outputs.py +0 -0
  77. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/analysis/params.py +0 -0
  78. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/analysis/workflow.py +0 -0
  79. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/cleaning/__init__.py +0 -0
  80. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/cleaning/_internal.py +0 -0
  81. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/cleaning/outputs.py +0 -0
  82. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/cleaning/params.py +0 -0
  83. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/cleaning/report.py +0 -0
  84. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/cleaning/workflow.py +0 -0
  85. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/coverage/__init__.py +0 -0
  86. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/coverage/ontology.py +0 -0
  87. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/coverage/outputs.py +0 -0
  88. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/coverage/params.py +0 -0
  89. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/coverage/report.py +0 -0
  90. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/drift/__init__.py +0 -0
  91. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/drift/outputs.py +0 -0
  92. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/drift/params.py +0 -0
  93. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/drift/report.py +0 -0
  94. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/drift/workflow.py +0 -0
  95. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/ood/__init__.py +0 -0
  96. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/ood/outputs.py +0 -0
  97. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/ood/params.py +0 -0
  98. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/ood/report.py +0 -0
  99. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/ood/workflow.py +0 -0
  100. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/parameter_sweep/__init__.py +0 -0
  101. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/parameter_sweep/outputs.py +0 -0
  102. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/parameter_sweep/params.py +0 -0
  103. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/parameter_sweep/workflow.py +0 -0
  104. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/prioritization/__init__.py +0 -0
  105. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/prioritization/outputs.py +0 -0
  106. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/prioritization/params.py +0 -0
  107. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/prioritization/report.py +0 -0
  108. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/prioritization/workflow.py +0 -0
  109. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/splitting/__init__.py +0 -0
  110. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/splitting/outputs.py +0 -0
  111. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/splitting/params.py +0 -0
  112. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/splitting/report.py +0 -0
  113. {dataeval_flow-0.2.2 → dataeval_flow-0.2.4}/src/dataeval_flow/workflows/splitting/workflow.py +0 -0
@@ -1,5 +1,6 @@
1
1
  # Environment
2
2
  .env
3
+ .env.local
3
4
  .venv/
4
5
  __pycache__/
5
6
  *.pyc
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dataeval-flow
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: DataEval Workflows container for data evaluation
5
5
  Project-URL: Homepage, https://github.com/aria-ml/dataeval-flow
6
6
  Project-URL: Documentation, https://dataeval-flow.readthedocs.io/
@@ -19,7 +19,7 @@ Classifier: Topic :: Scientific/Engineering
19
19
  Classifier: Typing :: Typed
20
20
  Requires-Python: <3.15,>=3.10
21
21
  Requires-Dist: click>=8.0
22
- Requires-Dist: dataeval>=1.1.0
22
+ Requires-Dist: dataeval>=1.1.1
23
23
  Requires-Dist: datamaite[ic,od]==0.4.1
24
24
  Requires-Dist: numpy>=1.24.2
25
25
  Requires-Dist: pydantic>=2.0
@@ -49,11 +49,9 @@ Requires-Dist: onnx>=1.15; extra == 'onnx-cu130'
49
49
  Requires-Dist: onnxruntime-gpu>=1.27; (python_version >= '3.11' and extra != 'onnx-cu126') and extra == 'onnx-cu130'
50
50
  Requires-Dist: onnxruntime>=1.20; (python_version == '3.10' and extra != 'onnx-cu126') and extra == 'onnx-cu130'
51
51
  Provides-Extra: ontology
52
- Requires-Dist: dataeval[ontology]==1.1.0; extra == 'ontology'
52
+ Requires-Dist: dataeval[ontology]>=1.1.1; extra == 'ontology'
53
53
  Provides-Extra: opencv
54
- Requires-Dist: opencv-python-headless>=4.8.0; extra == 'opencv'
55
- Provides-Extra: opencv-fips
56
- Requires-Dist: opencv-python-headless<4.13.0,>=4.8.0; extra == 'opencv-fips'
54
+ Requires-Dist: opencv-python-headless<4.13.0,>=4.8.0; extra == 'opencv'
57
55
  Description-Content-Type: text/markdown
58
56
 
59
57
  <!-- markdownlint-disable MD041 -->
@@ -165,16 +163,16 @@ at least the minimum CPU/memory above; size memory to your largest dataset.
165
163
 
166
164
  ```bash
167
165
  # 1. Build CUDA 12.6 container
168
- docker build -f docker/Dockerfile.cu126 -t dataeval:cu126 .
166
+ docker build -f docker/Dockerfile.cu126 -t dataeval-flow:cu126 .
169
167
 
170
168
  # 2. Show help
171
- docker run dataeval:cu126
169
+ docker run dataeval-flow:cu126
172
170
 
173
171
  # 3. Run with data and output
174
172
  docker run --gpus all \
175
173
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
176
174
  --mount type=bind,source=/path/to/output,target=/output \
177
- dataeval:cu126
175
+ dataeval-flow:cu126
178
176
  ```
179
177
 
180
178
  ## Pulling pre-built images
@@ -186,13 +184,13 @@ if you don't need to modify the code.
186
184
  **Rolling channel** — tracks the latest commit on `main`. The tag is overwritten on every merge.
187
185
 
188
186
  ```bash
189
- docker pull harbor.jatic.net/aria/dataeval:cu126 # cpu / cu126 / cu130
187
+ docker pull harbor.jatic.net/aria/dataeval-flow:latest-cu126 # cpu / cu126 / cu130
190
188
  ```
191
189
 
192
190
  **Pinned release channel** — immutable, version-tagged images cut from `v*` git tags. Use these for reproducible workloads.
193
191
 
194
192
  ```bash
195
- docker pull harbor.jatic.net/aria/dataeval:0.2.1-cu126
193
+ docker pull harbor.jatic.net/aria/dataeval-flow:0.2.4-cu126
196
194
  ```
197
195
 
198
196
  **Verifying the signature** — every published image is signed with
@@ -200,12 +198,12 @@ docker pull harbor.jatic.net/aria/dataeval:0.2.1-cu126
200
198
  [docker/cosign.pub](docker/cosign.pub).
201
199
 
202
200
  ```bash
203
- cosign verify --key docker/cosign.pub harbor.jatic.net/aria/dataeval:cu126
201
+ cosign verify --key docker/cosign.pub harbor.jatic.net/aria/dataeval-flow:latest-cu126
204
202
  ```
205
203
 
206
- Then drop the `dataeval:cu126` reference in the Quick Start `docker run`
207
- commands above with the fully-qualified `harbor.jatic.net/aria/dataeval:cu126`
208
- (or pinned version) and skip step 1.
204
+ Then replace the locally-built `dataeval-flow:cu126` reference in the Quick Start
205
+ `docker run` commands above with the fully-qualified
206
+ `harbor.jatic.net/aria/dataeval-flow:latest-cu126` (or a pinned version) and skip step 1.
209
207
 
210
208
  > **Note on feature branches.** Containers are only built and published from
211
209
  > `main` and release tags — no image is produced for MRs or topic branches. If
@@ -257,7 +255,7 @@ docker run --gpus all \
257
255
  --user "$(id -u):$(id -g)" \
258
256
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
259
257
  --mount type=bind,source=/path/to/output,target=/output \
260
- dataeval:cu126
258
+ dataeval-flow:cu126
261
259
  ```
262
260
 
263
261
  #### Option 2: Open directory permissions
@@ -279,7 +277,7 @@ docker run --gpus all \
279
277
  -e DATAEVAL_DATA=/data \
280
278
  --mount type=bind,source=/path/to/data,target=/data,readonly \
281
279
  --mount type=bind,source=/path/to/output,target=/output \
282
- dataeval:cu126
280
+ dataeval-flow:cu126
283
281
  ```
284
282
 
285
283
  ## Environment Variables
@@ -320,7 +318,7 @@ options, precedence, and examples — via its help command, which is also the
320
318
  default when the container runs with no pipeline arguments:
321
319
 
322
320
  ```bash
323
- docker run dataeval:cu126 --help
321
+ docker run dataeval-flow:cu126 --help
324
322
  ```
325
323
 
326
324
  The library form exposes the same options via `python -m dataeval_flow --help`.
@@ -340,13 +338,13 @@ To specify a config path explicitly:
340
338
  docker run --gpus all \
341
339
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
342
340
  --mount type=bind,source=/path/to/output,target=/output \
343
- dataeval:cu126 --config config/
341
+ dataeval-flow:cu126 --config config/
344
342
 
345
343
  # Single config file
346
344
  docker run --gpus all \
347
345
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
348
346
  --mount type=bind,source=/path/to/output,target=/output \
349
- dataeval:cu126 --config params.yaml
347
+ dataeval-flow:cu126 --config params.yaml
350
348
  ```
351
349
 
352
350
  Dataset and model paths in config files are resolved relative to the data root (`/dataeval` by default).
@@ -355,10 +353,10 @@ Dataset and model paths in config files are resolved relative to the data root (
355
353
 
356
354
  A `datasets` entry selects its loader with `format`:
357
355
 
358
- | `format` | Structure | Split selection |
359
- | -------------- | ------------------------------------------------------------- | ---------------------------------- |
360
- | `huggingface` | HuggingFace arrow dataset or dataset dict | `split:` (omit for a single split) |
361
- | `image_folder` | Directory of images, optionally one subdirectory per class | n/a |
356
+ | `format` | Structure | Split selection |
357
+ | -------------- | ------------------------------------------------------------- | ----------------------------------- |
358
+ | `huggingface` | HuggingFace arrow dataset or dataset dict | `split:` (omit for a single split) |
359
+ | `image_folder` | Directory of images, optionally one subdirectory per class | n/a |
362
360
  | `coco` | COCO images plus a JSON annotation file | `annotations_file:` + `images_dir:` |
363
361
  | `yolo` | Ultralytics root — `data.yaml` plus image/label trees | `split:` (omit to load every split) |
364
362
 
@@ -373,12 +371,12 @@ guide for the full field list of each format.
373
371
  For machines without NVIDIA GPU:
374
372
 
375
373
  ```bash
376
- docker build -f docker/Dockerfile.cpu -t dataeval:cpu .
377
- docker run dataeval:cpu # Shows help
374
+ docker build -f docker/Dockerfile.cpu -t dataeval-flow:cpu .
375
+ docker run dataeval-flow:cpu # Shows help
378
376
  docker run \
379
377
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
380
378
  --mount type=bind,source=/path/to/output,target=/output \
381
- dataeval:cpu
379
+ dataeval-flow:cpu
382
380
  ```
383
381
 
384
382
  ## CLI Modes
@@ -549,7 +547,7 @@ pip install dataeval-flow
549
547
  `torchvision` is not installed by default — it is imported lazily and is only needed
550
548
  for preprocessing pipelines, the torchvision dataset adapter, and the TUI's transform
551
549
  discovery. Install it together with `torch` in step 1 so both come from the same index.
552
- Feature extras (`onnx`, `onnx-cu126`, `onnx-cu130`, `opencv`, `opencv-fips`, `app`, `ontology`) work normally under
550
+ Feature extras (`onnx`, `onnx-cu126`, `onnx-cu130`, `opencv`, `app`, `ontology`) work normally under
553
551
  pip and are independent of the PyTorch variant:
554
552
 
555
553
  ```bash
@@ -107,16 +107,16 @@ at least the minimum CPU/memory above; size memory to your largest dataset.
107
107
 
108
108
  ```bash
109
109
  # 1. Build CUDA 12.6 container
110
- docker build -f docker/Dockerfile.cu126 -t dataeval:cu126 .
110
+ docker build -f docker/Dockerfile.cu126 -t dataeval-flow:cu126 .
111
111
 
112
112
  # 2. Show help
113
- docker run dataeval:cu126
113
+ docker run dataeval-flow:cu126
114
114
 
115
115
  # 3. Run with data and output
116
116
  docker run --gpus all \
117
117
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
118
118
  --mount type=bind,source=/path/to/output,target=/output \
119
- dataeval:cu126
119
+ dataeval-flow:cu126
120
120
  ```
121
121
 
122
122
  ## Pulling pre-built images
@@ -128,13 +128,13 @@ if you don't need to modify the code.
128
128
  **Rolling channel** — tracks the latest commit on `main`. The tag is overwritten on every merge.
129
129
 
130
130
  ```bash
131
- docker pull harbor.jatic.net/aria/dataeval:cu126 # cpu / cu126 / cu130
131
+ docker pull harbor.jatic.net/aria/dataeval-flow:latest-cu126 # cpu / cu126 / cu130
132
132
  ```
133
133
 
134
134
  **Pinned release channel** — immutable, version-tagged images cut from `v*` git tags. Use these for reproducible workloads.
135
135
 
136
136
  ```bash
137
- docker pull harbor.jatic.net/aria/dataeval:0.2.1-cu126
137
+ docker pull harbor.jatic.net/aria/dataeval-flow:0.2.4-cu126
138
138
  ```
139
139
 
140
140
  **Verifying the signature** — every published image is signed with
@@ -142,12 +142,12 @@ docker pull harbor.jatic.net/aria/dataeval:0.2.1-cu126
142
142
  [docker/cosign.pub](docker/cosign.pub).
143
143
 
144
144
  ```bash
145
- cosign verify --key docker/cosign.pub harbor.jatic.net/aria/dataeval:cu126
145
+ cosign verify --key docker/cosign.pub harbor.jatic.net/aria/dataeval-flow:latest-cu126
146
146
  ```
147
147
 
148
- Then drop the `dataeval:cu126` reference in the Quick Start `docker run`
149
- commands above with the fully-qualified `harbor.jatic.net/aria/dataeval:cu126`
150
- (or pinned version) and skip step 1.
148
+ Then replace the locally-built `dataeval-flow:cu126` reference in the Quick Start
149
+ `docker run` commands above with the fully-qualified
150
+ `harbor.jatic.net/aria/dataeval-flow:latest-cu126` (or a pinned version) and skip step 1.
151
151
 
152
152
  > **Note on feature branches.** Containers are only built and published from
153
153
  > `main` and release tags — no image is produced for MRs or topic branches. If
@@ -199,7 +199,7 @@ docker run --gpus all \
199
199
  --user "$(id -u):$(id -g)" \
200
200
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
201
201
  --mount type=bind,source=/path/to/output,target=/output \
202
- dataeval:cu126
202
+ dataeval-flow:cu126
203
203
  ```
204
204
 
205
205
  #### Option 2: Open directory permissions
@@ -221,7 +221,7 @@ docker run --gpus all \
221
221
  -e DATAEVAL_DATA=/data \
222
222
  --mount type=bind,source=/path/to/data,target=/data,readonly \
223
223
  --mount type=bind,source=/path/to/output,target=/output \
224
- dataeval:cu126
224
+ dataeval-flow:cu126
225
225
  ```
226
226
 
227
227
  ## Environment Variables
@@ -262,7 +262,7 @@ options, precedence, and examples — via its help command, which is also the
262
262
  default when the container runs with no pipeline arguments:
263
263
 
264
264
  ```bash
265
- docker run dataeval:cu126 --help
265
+ docker run dataeval-flow:cu126 --help
266
266
  ```
267
267
 
268
268
  The library form exposes the same options via `python -m dataeval_flow --help`.
@@ -282,13 +282,13 @@ To specify a config path explicitly:
282
282
  docker run --gpus all \
283
283
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
284
284
  --mount type=bind,source=/path/to/output,target=/output \
285
- dataeval:cu126 --config config/
285
+ dataeval-flow:cu126 --config config/
286
286
 
287
287
  # Single config file
288
288
  docker run --gpus all \
289
289
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
290
290
  --mount type=bind,source=/path/to/output,target=/output \
291
- dataeval:cu126 --config params.yaml
291
+ dataeval-flow:cu126 --config params.yaml
292
292
  ```
293
293
 
294
294
  Dataset and model paths in config files are resolved relative to the data root (`/dataeval` by default).
@@ -297,10 +297,10 @@ Dataset and model paths in config files are resolved relative to the data root (
297
297
 
298
298
  A `datasets` entry selects its loader with `format`:
299
299
 
300
- | `format` | Structure | Split selection |
301
- | -------------- | ------------------------------------------------------------- | ---------------------------------- |
302
- | `huggingface` | HuggingFace arrow dataset or dataset dict | `split:` (omit for a single split) |
303
- | `image_folder` | Directory of images, optionally one subdirectory per class | n/a |
300
+ | `format` | Structure | Split selection |
301
+ | -------------- | ------------------------------------------------------------- | ----------------------------------- |
302
+ | `huggingface` | HuggingFace arrow dataset or dataset dict | `split:` (omit for a single split) |
303
+ | `image_folder` | Directory of images, optionally one subdirectory per class | n/a |
304
304
  | `coco` | COCO images plus a JSON annotation file | `annotations_file:` + `images_dir:` |
305
305
  | `yolo` | Ultralytics root — `data.yaml` plus image/label trees | `split:` (omit to load every split) |
306
306
 
@@ -315,12 +315,12 @@ guide for the full field list of each format.
315
315
  For machines without NVIDIA GPU:
316
316
 
317
317
  ```bash
318
- docker build -f docker/Dockerfile.cpu -t dataeval:cpu .
319
- docker run dataeval:cpu # Shows help
318
+ docker build -f docker/Dockerfile.cpu -t dataeval-flow:cpu .
319
+ docker run dataeval-flow:cpu # Shows help
320
320
  docker run \
321
321
  --mount type=bind,source=/path/to/data,target=/dataeval,readonly \
322
322
  --mount type=bind,source=/path/to/output,target=/output \
323
- dataeval:cpu
323
+ dataeval-flow:cpu
324
324
  ```
325
325
 
326
326
  ## CLI Modes
@@ -491,7 +491,7 @@ pip install dataeval-flow
491
491
  `torchvision` is not installed by default — it is imported lazily and is only needed
492
492
  for preprocessing pipelines, the torchvision dataset adapter, and the TUI's transform
493
493
  discovery. Install it together with `torch` in step 1 so both come from the same index.
494
- Feature extras (`onnx`, `onnx-cu126`, `onnx-cu130`, `opencv`, `opencv-fips`, `app`, `ontology`) work normally under
494
+ Feature extras (`onnx`, `onnx-cu126`, `onnx-cu130`, `opencv`, `app`, `ontology`) work normally under
495
495
  pip and are independent of the PyTorch variant:
496
496
 
497
497
  ```bash
@@ -19,7 +19,7 @@ classifiers = [
19
19
  ]
20
20
  dependencies = [
21
21
  "click>=8.0",
22
- "dataeval>=1.1.0",
22
+ "dataeval>=1.1.1",
23
23
  "datamaite[ic,od]==0.4.1",
24
24
  "numpy>=1.24.2",
25
25
  "pydantic>=2.0",
@@ -51,10 +51,9 @@ onnx-cu130 = [
51
51
  "onnxruntime>=1.20; python_version == '3.10' and extra != 'onnx-cu126'", # CUDA 13.0 is not supported on Python 3.10
52
52
  "onnxruntime-gpu>=1.27; python_version >= '3.11' and extra != 'onnx-cu126'",
53
53
  ]
54
- opencv = ["opencv-python-headless>=4.8.0"]
55
- opencv-fips = ["opencv-python-headless>=4.8.0,<4.13.0"] # OpenCV 4.13 and higher are not FIPS compliant
54
+ opencv = ["opencv-python-headless>=4.8.0,<4.13.0"] # OpenCV 4.13 and higher are not FIPS compliant
56
55
  app = ["textual>=3.0"]
57
- ontology = ["dataeval[ontology]==1.1.0"]
56
+ ontology = ["dataeval[ontology]>=1.1.1"]
58
57
 
59
58
  [project.urls]
60
59
  Homepage = "https://github.com/aria-ml/dataeval-flow"
@@ -167,10 +166,6 @@ conflicts = [
167
166
  { extra = "onnx" },
168
167
  { extra = "onnx-cu126" },
169
168
  { extra = "onnx-cu130" },
170
- ],
171
- [
172
- { extra = "opencv" },
173
- { extra = "opencv-fips" },
174
169
  ]
175
170
  ]
176
171
 
@@ -332,7 +327,7 @@ ignore = [
332
327
  "C416",
333
328
  ]
334
329
  fixable = ["ALL"]
335
- per-file-ignores = { "tests/*" = ["ANN", "S101", "D", "SLF001", "ARG002", "PT019"], "docs/*" = ["D", "E402", "E501", "S101"], "verification/*" = ["ANN", "S101", "D", "SLF001", "ARG001", "ARG002", "PT011", "PT019"] }
330
+ per-file-ignores = { "tests/*" = ["ANN", "S101", "D", "SLF001", "ARG002", "PT019"], "docs/*" = ["D", "E402", "E501", "S101"], "verification/*" = ["ANN", "S101", "D", "SLF001", "ARG001", "ARG002", "PT011", "PT019"], "scripts/*" = ["C901", "D", "S"] }
336
331
 
337
332
  [tool.ruff.lint.pydocstyle]
338
333
  convention = "pep257"
@@ -347,5 +342,5 @@ indent-style = "space"
347
342
  # --- Codespell ---
348
343
 
349
344
  [tool.codespell]
350
- skip = "./*env*,./output,./htmlcov,./.nox,*.lock,./docs/source/notebooks/*,./docker/cosign.pub,./docker/Dockerfile.*,*.onnx"
345
+ skip = "./*env*,./output,./specs,./htmlcov,./.nox,*.lock,./docs/source/notebooks/*,./docker/cosign.pub,./docker/Dockerfile.*,*.onnx"
351
346
  ignore-words-list = ["statics"]
@@ -3,11 +3,12 @@
3
3
 
4
4
  import argparse
5
5
  import logging
6
- import os
7
6
  import sys
8
7
  from pathlib import Path
9
8
  from typing import NoReturn
10
9
 
10
+ from dataeval_flow._env import env_bool, env_choice, env_int, env_list, env_path
11
+
11
12
  _logger = logging.getLogger(__name__)
12
13
 
13
14
 
@@ -41,36 +42,43 @@ def _build_parser() -> argparse.ArgumentParser:
41
42
  "-c",
42
43
  "--config",
43
44
  type=Path,
44
- default=None,
45
- help="Path to config file or folder. If omitted, auto-discovers YAML/JSON at the data root.",
45
+ default=env_path("DATAEVAL_CONFIG"),
46
+ help=(
47
+ "Path to config file or folder (default: $DATAEVAL_CONFIG). If omitted, "
48
+ "auto-discovers YAML/JSON at the data root."
49
+ ),
46
50
  )
47
-
48
- _data_default = os.environ.get("DATAEVAL_DATA")
49
51
  parser.add_argument(
50
52
  "-d",
51
53
  "--data",
52
54
  type=Path,
53
- default=Path(_data_default) if _data_default else None,
55
+ default=env_path("DATAEVAL_DATA"),
54
56
  help="Root directory for data files (default: $DATAEVAL_DATA or current directory)",
55
57
  )
56
-
57
- _output_default = os.environ.get("DATAEVAL_OUTPUT")
58
58
  parser.add_argument(
59
59
  "-o",
60
60
  "--output",
61
61
  type=Path,
62
- default=Path(_output_default) if _output_default else None,
62
+ default=env_path("DATAEVAL_OUTPUT"),
63
63
  help="Path to output directory for artifacts (default: $DATAEVAL_OUTPUT or None).",
64
64
  )
65
-
66
- _cache_default = os.environ.get("DATAEVAL_CACHE")
67
65
  parser.add_argument(
68
66
  "-k",
69
67
  "--cache",
70
68
  type=Path,
71
- default=Path(_cache_default) if _cache_default else None,
69
+ default=env_path("DATAEVAL_CACHE"),
72
70
  help="Directory for disk-backed computation cache (default: $DATAEVAL_CACHE or None).",
73
71
  )
72
+ parser.add_argument(
73
+ "--log-format",
74
+ choices=("structured", "plain"),
75
+ default=env_choice("DATAEVAL_LOG_FORMAT", ("structured", "plain")) or "structured",
76
+ help=(
77
+ "Console log format (default: $DATAEVAL_LOG_FORMAT, else structured). "
78
+ "'structured' prefixes each record with an ISO-8601 UTC timestamp and level; "
79
+ "'plain' prints bare messages."
80
+ ),
81
+ )
74
82
  parser.add_argument(
75
83
  "-t",
76
84
  "--task",
@@ -85,10 +93,12 @@ def _build_parser() -> argparse.ArgumentParser:
85
93
  )
86
94
  parser.add_argument(
87
95
  "--fail-on-warning",
88
- action="store_true",
96
+ action=argparse.BooleanOptionalAction,
97
+ default=env_bool("DATAEVAL_FAIL_ON_WARNING") or False,
89
98
  help=(
90
99
  "Exit non-zero when a task succeeds but reports findings that breached their "
91
- "health thresholds. Off by default, so a warning stays a prompt to look."
100
+ "health thresholds (default: $DATAEVAL_FAIL_ON_WARNING, else off). "
101
+ "Use --no-fail-on-warning to override the environment."
92
102
  ),
93
103
  )
94
104
 
@@ -131,14 +141,14 @@ def _build_parser() -> argparse.ArgumentParser:
131
141
  "-d",
132
142
  "--data",
133
143
  type=Path,
134
- default=None,
144
+ default=env_path("DATAEVAL_DATA"),
135
145
  help="Root directory for data files (default: $DATAEVAL_DATA or current directory)",
136
146
  )
137
147
  app_parser.add_argument(
138
148
  "-k",
139
149
  "--cache",
140
150
  type=Path,
141
- default=None,
151
+ default=env_path("DATAEVAL_CACHE"),
142
152
  help="Directory for disk-backed computation cache (embeddings, metadata, stats).",
143
153
  )
144
154
 
@@ -157,6 +167,7 @@ def _build_parser() -> argparse.ArgumentParser:
157
167
  type=Path,
158
168
  help="Path to a result.json written by a run",
159
169
  )
170
+ # Do not read DATAEVAL_OUTPUT here: encoding defaults to stdout unless -o is explicitly specified.
160
171
  encoding_parser.add_argument(
161
172
  "-o",
162
173
  "--output",
@@ -221,15 +232,37 @@ def _list_workflows(name: str | None, *, as_json: bool) -> int:
221
232
  return 0
222
233
 
223
234
 
235
+ def apply_env_defaults(args: argparse.Namespace) -> argparse.Namespace:
236
+ """Apply environment variable defaults that cannot be handled by argparse defaults.
237
+
238
+ For options with ``count`` or ``append`` actions (such as ``--verbose`` and
239
+ ``--task``), setting defaults in argparse causes command-line arguments to
240
+ increment or append to the default instead of overriding it. Applying these
241
+ environment variables post-parsing ensures CLI arguments take precedence.
242
+
243
+ ``DATAEVAL_TASKS`` applies only to headless execution, not subcommands.
244
+ """
245
+ if getattr(args, "verbose", 0) == 0:
246
+ args.verbose = env_int("DATAEVAL_VERBOSITY") or 0
247
+ if args.command is None and getattr(args, "task", None) is None:
248
+ args.task = env_list("DATAEVAL_TASKS")
249
+ return args
250
+
251
+
224
252
  def parse_args() -> argparse.Namespace:
225
- """Parse CLI arguments."""
253
+ """Parse CLI arguments and apply environment defaults."""
226
254
  parser = _build_parser()
227
- return parser.parse_args()
255
+ return apply_env_defaults(parser.parse_args())
228
256
 
229
257
 
230
258
  def main() -> NoReturn:
231
259
  """CLI entry point."""
232
- args = parse_args()
260
+ try:
261
+ args = parse_args()
262
+ except ValueError as e:
263
+ # Report invalid environment variable values without a traceback.
264
+ print(f"ERROR: {e}", file=sys.stderr)
265
+ sys.exit(1)
233
266
 
234
267
  if args.command == "app":
235
268
  try:
@@ -256,7 +289,7 @@ def main() -> NoReturn:
256
289
  # refused, and the "commit it" hand-off when it succeeds — is dropped and the user
257
290
  # is left with a bare exit code. At INFO because this command's whole output is
258
291
  # one artifact and one sentence saying where it went.
259
- setup_logging(verbosity=max(args.verbose, 2))
292
+ setup_logging(verbosity=max(args.verbose, 2), log_format=args.log_format)
260
293
  sys.exit(write_encoding(args.result, args.output, args.task))
261
294
 
262
295
  if args.command == "workflows":
@@ -273,7 +306,7 @@ def main() -> NoReturn:
273
306
  # resolution are reported even before the runner configures the file log.
274
307
  from dataeval_flow._logging import setup_logging
275
308
 
276
- setup_logging(verbosity=args.verbose)
309
+ setup_logging(verbosity=args.verbose, log_format=args.log_format)
277
310
  try:
278
311
  from dataeval_flow.runner import run
279
312
 
@@ -0,0 +1,74 @@
1
+ """Environment variable parsing for CLI options.
2
+
3
+ Functions return ``None`` when the variable is unset or blank. Invalid values
4
+ raise ``ValueError``.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+
10
+ __all__ = ["env_bool", "env_choice", "env_int", "env_list", "env_path"]
11
+
12
+ _TRUE = ("1", "true", "yes", "on")
13
+ _FALSE = ("0", "false", "no", "off")
14
+
15
+
16
+ def _raw(name: str) -> str | None:
17
+ """Return the stripped value of *name*, or None when unset or blank."""
18
+ value = os.environ.get(name)
19
+ if value is None:
20
+ return None
21
+ stripped = value.strip()
22
+ return stripped or None
23
+
24
+
25
+ def env_path(name: str) -> Path | None:
26
+ """Read *name* as a filesystem path."""
27
+ raw = _raw(name)
28
+ return Path(raw) if raw is not None else None
29
+
30
+
31
+ def env_int(name: str) -> int | None:
32
+ """Read *name* as an integer, raising ValueError if invalid."""
33
+ raw = _raw(name)
34
+ if raw is None:
35
+ return None
36
+ try:
37
+ return int(raw)
38
+ except ValueError:
39
+ raise ValueError(f"{name} must be an integer, got {raw!r}") from None
40
+
41
+
42
+ def env_bool(name: str) -> bool | None:
43
+ """Read *name* as a boolean, raising ValueError if unrecognized."""
44
+ raw = _raw(name)
45
+ if raw is None:
46
+ return None
47
+ lowered = raw.lower()
48
+ if lowered in _TRUE:
49
+ return True
50
+ if lowered in _FALSE:
51
+ return False
52
+ accepted = ", ".join((*_TRUE, *_FALSE))
53
+ raise ValueError(f"{name} must be one of {accepted}; got {raw!r}")
54
+
55
+
56
+ def env_list(name: str) -> list[str] | None:
57
+ """Read *name* as a comma-separated list, raising ValueError if empty."""
58
+ raw = _raw(name)
59
+ if raw is None:
60
+ return None
61
+ items = [item.strip() for item in raw.split(",") if item.strip()]
62
+ if not items:
63
+ raise ValueError(f"{name} was set but lists no values: {raw!r}")
64
+ return items
65
+
66
+
67
+ def env_choice(name: str, choices: tuple[str, ...]) -> str | None:
68
+ """Read *name* and validate that its value is in *choices*."""
69
+ raw = _raw(name)
70
+ if raw is None:
71
+ return None
72
+ if raw not in choices:
73
+ raise ValueError(f"{name} must be one of {', '.join(choices)}; got {raw!r}")
74
+ return raw
@@ -32,22 +32,31 @@ _FILE_ROLE = "_dataeval_flow_file"
32
32
  _FILE_FORMAT = "%(asctime)s [%(levelname)-5s] %(name)s: %(message)s"
33
33
  _FILE_DATEFMT = "%Y-%m-%dT%H:%M:%SZ"
34
34
 
35
+ # Console format for structured mode (ISO-8601 UTC timestamp and log level).
36
+ _CONSOLE_FORMAT = "%(asctime)s [%(levelname)s] %(message)s"
37
+ _CONSOLE_DATEFMT = "%Y-%m-%dT%H:%M:%SZ"
38
+
35
39
 
36
40
  class _ConsoleFormatter(logging.Formatter):
37
- """Clean console formatter for CLI/container output.
41
+ """Console formatter for CLI and container output.
38
42
 
39
- Strips the library-style prefix (timestamp, logger name) so user-facing
40
- output reads like plain program output. INFO/DEBUG records render as just
41
- the message; WARNING and above are tagged with ``LEVEL:`` so problems stay
42
- visible. Tracebacks (``exc_info``) are appended via the standard machinery.
43
+ Supports ``structured`` mode (ISO-8601 UTC timestamp and level prefix)
44
+ and ``plain`` mode (bare messages, with a level prefix only for WARNING
45
+ and above).
43
46
  """
44
47
 
45
- def __init__(self) -> None:
46
- super().__init__("%(message)s")
47
- self._warn_formatter = logging.Formatter("%(levelname)s: %(message)s")
48
+ def __init__(self, log_format: str = "structured") -> None:
49
+ if log_format == "plain":
50
+ super().__init__("%(message)s")
51
+ self._warn_formatter: logging.Formatter | None = logging.Formatter("%(levelname)s: %(message)s")
52
+ else:
53
+ super().__init__(_CONSOLE_FORMAT, datefmt=_CONSOLE_DATEFMT)
54
+ # Ensure timestamps format in UTC
55
+ self.converter = time.gmtime
56
+ self._warn_formatter = None
48
57
 
49
58
  def format(self, record: logging.LogRecord) -> str:
50
- if record.levelno >= logging.WARNING:
59
+ if self._warn_formatter is not None and record.levelno >= logging.WARNING:
51
60
  return self._warn_formatter.format(record)
52
61
  return super().format(record)
53
62
 
@@ -72,7 +81,11 @@ class LogMessage:
72
81
  return self._str
73
82
 
74
83
 
75
- def setup_logging(output_dir: Path | None = None, verbosity: int = 0) -> None:
84
+ def setup_logging(
85
+ output_dir: Path | None = None,
86
+ verbosity: int = 0,
87
+ log_format: str = "structured",
88
+ ) -> None:
76
89
  """Configure root logger with a clean console handler and optional file log.
77
90
 
78
91
  Additive and idempotent: the console (stdout) handler and the file handler
@@ -81,10 +94,11 @@ def setup_logging(output_dir: Path | None = None, verbosity: int = 0) -> None:
81
94
  file handler once ``output_dir`` is known — without ever duplicating a
82
95
  handler.
83
96
 
84
- The console handler uses :class:`_ConsoleFormatter` (bare messages, with a
85
- ``LEVEL:`` prefix only for warnings and above) so CLI/container output reads
86
- like plain program output. The file handler keeps the full timestamped,
87
- named format at DEBUG for diagnostics.
97
+ The console handler uses :class:`_ConsoleFormatter` in either
98
+ ``"structured"`` mode (ISO-8601 UTC timestamp and level prefix) or
99
+ ``"plain"`` mode (bare messages, with a level prefix only for WARNING
100
+ and above). The file handler retains the timestamped format at DEBUG
101
+ level.
88
102
 
89
103
  Parameters
90
104
  ----------
@@ -93,6 +107,9 @@ def setup_logging(output_dir: Path | None = None, verbosity: int = 0) -> None:
93
107
  handler is created and output is console-only.
94
108
  verbosity : int
95
109
  Console verbosity level (0=quiet, 1=report, 2=+INFO, 3=+DEBUG).
110
+ log_format : str
111
+ Console format: ``"structured"`` (timestamp and level prefix) or
112
+ ``"plain"`` (bare messages). The file handler is unaffected.
96
113
  """
97
114
  global _initialized
98
115
  _initialized = True
@@ -110,7 +127,7 @@ def setup_logging(output_dir: Path | None = None, verbosity: int = 0) -> None:
110
127
  sh.setLevel(logging.INFO)
111
128
  else:
112
129
  sh.setLevel(logging.WARNING)
113
- sh.setFormatter(_ConsoleFormatter())
130
+ sh.setFormatter(_ConsoleFormatter(log_format))
114
131
  setattr(sh, _CONSOLE_ROLE, True)
115
132
  root.addHandler(sh)
116
133
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.2'
22
- __version_tuple__ = version_tuple = (0, 2, 2)
21
+ __version__ = version = '0.2.4'
22
+ __version_tuple__ = version_tuple = (0, 2, 4)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -10,6 +10,7 @@ Assessments are organized by the dimension they evaluate:
10
10
  """
11
11
 
12
12
  import contextlib
13
+ import inspect
13
14
  import logging
14
15
  import warnings
15
16
  from typing import Any
@@ -20,6 +21,7 @@ from dataeval import Metadata
20
21
  from dataeval.core import label_stats
21
22
  from dataeval.protocols import AnnotatedDataset, ObjectDetectionTarget
22
23
  from pydantic import BaseModel
24
+ from typing_extensions import get_protocol_members
23
25
 
24
26
  from dataeval_flow.binning import attach_binning
25
27
  from dataeval_flow.cache import active_cache, get_or_compute_embeddings, get_or_compute_metadata
@@ -149,12 +151,25 @@ def _run_coverage(
149
151
  )
150
152
 
151
153
 
154
+ #: Protocol members declared by ObjectDetectionTarget.
155
+ _OD_TARGET_MEMBERS = get_protocol_members(ObjectDetectionTarget)
156
+
157
+ _MISSING = object()
158
+
159
+
152
160
  def _is_object_detection(dataset: AnnotatedDataset[Any]) -> bool:
153
- """Whether the dataset's targets are detections rather than one label per image."""
161
+ """Check whether dataset targets implement the ObjectDetectionTarget protocol.
162
+
163
+ Uses :func:`inspect.getattr_static` to verify member presence without
164
+ invoking property getters, ensuring consistent behavior across Python
165
+ versions.
166
+ """
154
167
  if len(dataset) == 0:
155
168
  return False
156
169
  datum = dataset[0]
157
- return isinstance(datum, tuple) and len(datum) == 3 and isinstance(datum[1], ObjectDetectionTarget)
170
+ if not isinstance(datum, tuple) or len(datum) != 3:
171
+ return False
172
+ return all(inspect.getattr_static(datum[1], name, _MISSING) is not _MISSING for name in _OD_TARGET_MEMBERS)
158
173
 
159
174
 
160
175
  def _crop_view(
File without changes