euler-train 2.0.0__tar.gz → 2.1.0__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.
- {euler_train-2.0.0 → euler_train-2.1.0}/PKG-INFO +1 -1
- {euler_train-2.0.0 → euler_train-2.1.0}/README.md +30 -2
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train/__init__.py +11 -1
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train/outputs.py +145 -18
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train/run.py +7 -1
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train.egg-info/PKG-INFO +1 -1
- {euler_train-2.0.0 → euler_train-2.1.0}/pyproject.toml +1 -1
- {euler_train-2.0.0 → euler_train-2.1.0}/tests/test_runlog.py +55 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train/_dataset_contract.py +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train/architecture.py +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train/environment.py +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train/git_info.py +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train/serialization.py +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train/slurm.py +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train.egg-info/SOURCES.txt +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train.egg-info/dependency_links.txt +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train.egg-info/requires.txt +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/euler_train.egg-info/top_level.txt +0 -0
- {euler_train-2.0.0 → euler_train-2.1.0}/setup.cfg +0 -0
|
@@ -80,7 +80,7 @@ The run ID and directory are available as `run.run_id` and `run.dir`.
|
|
|
80
80
|
|
|
81
81
|
## API reference
|
|
82
82
|
|
|
83
|
-
### `euler_train.init(dir, config=None, meta=None, output_formats=None, run_id=None, datasets=None, run_name=None, evaluations=None, mode=None) → Run`
|
|
83
|
+
### `euler_train.init(dir, config=None, meta=None, output_formats=None, output_visualization=None, run_id=None, datasets=None, run_name=None, evaluations=None, mode=None) → Run`
|
|
84
84
|
|
|
85
85
|
Creates the run directory and writes `meta.json`, `config.json`, `code_ref.json`, and `run_environment.json`. On resume (`run_id` provided), only `meta.json` and `config.json` are updated.
|
|
86
86
|
|
|
@@ -90,6 +90,7 @@ Creates the run directory and writes `meta.json`, `config.json`, `code_ref.json`
|
|
|
90
90
|
| `config` | `dict \| str \| Path \| Namespace \| dataclass` | Hyperparameters. Paths to `.json` / `.yaml` files are loaded automatically. |
|
|
91
91
|
| `meta` | `dict \| None` | Extra fields merged into `meta.json` (e.g. `{"tags": ["baseline"]}`). |
|
|
92
92
|
| `output_formats` | `dict[str, str] \| None` | Override format inference (see [Format inference](#format-inference)). |
|
|
93
|
+
| `output_visualization` | `dict[str, Any] \| None` | Override PNG visualization policy per output type / slot (see [Output visualization](#output-visualization)). |
|
|
93
94
|
| `run_id` | `str \| None` | Resume an existing run at `{dir}/runs/{run_id}` instead of creating a new one. |
|
|
94
95
|
| `datasets` | `dict[str, Any] \| None` | Optional split → dataset map. If a dataset exposes `describe_for_runlog()`, that contract is used directly; otherwise euler_train infers structured modality metadata (`path`, `used_as`, `slot`, `modality_type`, and hierarchical fields), resolving fixed namespaced properties from `properties.euler_loading` and `properties.euler_train` before heuristics. |
|
|
95
96
|
| `run_name` | `str \| None` | Optional human-readable run label stored in `meta.json`. |
|
|
@@ -218,7 +219,7 @@ Arrays are saved as `.png` or `.npy` based on shape and dtype:
|
|
|
218
219
|
|---|---|
|
|
219
220
|
| `uint8` with shape `(H,W)` | `.png` (grayscale) |
|
|
220
221
|
| Any dtype with shape `(H,W,1)`, `(H,W,3)`, `(H,W,4)` | `.png` |
|
|
221
|
-
| Float `.png` |
|
|
222
|
+
| Float `.png` | defaults to `unit_range`: clip to `[0,1]`, then scale to `[0,255]` |
|
|
222
223
|
| Everything else (e.g. `float32 (H,W)`) | `.npy` |
|
|
223
224
|
| PIL Image | `.png` |
|
|
224
225
|
|
|
@@ -240,6 +241,33 @@ run = euler_train.init(
|
|
|
240
241
|
|
|
241
242
|
Supported formats: `"png"`, `"npy"`, `"npz"`.
|
|
242
243
|
|
|
244
|
+
## Output visualization
|
|
245
|
+
|
|
246
|
+
Float PNG outputs use a rendering policy before quantization to `uint8`. Policies are configured once at `init()` via `output_visualization`, and keys resolve most-specific-first just like `output_formats`:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
run = euler_train.init(
|
|
250
|
+
dir="runs/exp",
|
|
251
|
+
config=cfg,
|
|
252
|
+
output_visualization={
|
|
253
|
+
"depth": {"mode": "percentile", "pmin": 1, "pmax": 99},
|
|
254
|
+
"depth.pred": {"mode": "fixed_range", "vmin": 0.0, "vmax": 80.0},
|
|
255
|
+
"confidence": "minmax",
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Supported visualization modes:
|
|
261
|
+
|
|
262
|
+
| Mode | Parameters | Description |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| `unit_range` | none | Clip float data to `[0,1]` before saving. |
|
|
265
|
+
| `minmax` | none | Normalize using the finite-value min / max of the saved item. |
|
|
266
|
+
| `percentile` | `pmin`, `pmax` | Normalize using finite-value percentiles, useful for depth-like outputs. |
|
|
267
|
+
| `fixed_range` | `vmin`, `vmax` | Normalize against an explicit numeric range. |
|
|
268
|
+
|
|
269
|
+
All float PNG policies also accept `invert: true` to flip black ↔ white after normalization.
|
|
270
|
+
|
|
243
271
|
## `meta.json` schema
|
|
244
272
|
|
|
245
273
|
Auto-managed, not written to directly.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""runlog — lightweight file-based experiment logging."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
4
6
|
from . import _dataset_contract # noqa: F401
|
|
5
7
|
from .run import Run
|
|
6
8
|
from .architecture import export_architecture
|
|
@@ -14,6 +16,7 @@ def init(
|
|
|
14
16
|
config=None,
|
|
15
17
|
meta: dict | None = None,
|
|
16
18
|
output_formats: dict[str, str] | None = None,
|
|
19
|
+
output_visualization: dict[str, Any] | None = None,
|
|
17
20
|
run_id: str | None = None,
|
|
18
21
|
datasets: dict | None = None,
|
|
19
22
|
run_name: str | None = None,
|
|
@@ -51,6 +54,11 @@ def init(
|
|
|
51
54
|
(``"depth"``), a slot / aux name (``"transmission"``), or a
|
|
52
55
|
dotted combination (``"depth.pred"``). Values are ``"png"``,
|
|
53
56
|
``"npy"``, or ``"npz"``.
|
|
57
|
+
output_visualization:
|
|
58
|
+
Optional PNG rendering policy overrides. Keys resolve the same way
|
|
59
|
+
as *output_formats* (``"depth.pred"`` → ``"depth"`` → ``"pred"``).
|
|
60
|
+
Values can be a string mode (for example ``"percentile"``) or a
|
|
61
|
+
mapping such as ``{"mode": "fixed_range", "vmin": 0, "vmax": 80}``.
|
|
54
62
|
run_id:
|
|
55
63
|
If given, resume an existing run instead of creating a new one.
|
|
56
64
|
The run directory ``{dir}/runs/{run_id}/`` must already exist.
|
|
@@ -84,7 +92,9 @@ def init(
|
|
|
84
92
|
"""
|
|
85
93
|
return Run(
|
|
86
94
|
dir=dir, config=config, meta=meta,
|
|
87
|
-
output_formats=output_formats,
|
|
95
|
+
output_formats=output_formats,
|
|
96
|
+
output_visualization=output_visualization,
|
|
97
|
+
run_id=run_id,
|
|
88
98
|
datasets=datasets, run_name=run_name,
|
|
89
99
|
evaluations=evaluations, mode=mode,
|
|
90
100
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Save prediction / ground-truth / auxiliary outputs to disk."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
from collections.abc import Mapping
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
@@ -15,6 +16,7 @@ def save_output_tree(
|
|
|
15
16
|
type_dir: Path,
|
|
16
17
|
slots: dict[str, Any],
|
|
17
18
|
format_overrides: dict[str, str],
|
|
19
|
+
visualization_overrides: dict[str, Any],
|
|
18
20
|
output_type: str,
|
|
19
21
|
) -> None:
|
|
20
22
|
"""Persist all slots (pred, gt, input, aux/…) for one *output_type*.
|
|
@@ -35,13 +37,21 @@ def save_output_tree(
|
|
|
35
37
|
if aux_data is None:
|
|
36
38
|
continue
|
|
37
39
|
_save_slot(
|
|
38
|
-
type_dir / "aux" / aux_name,
|
|
39
|
-
|
|
40
|
+
type_dir / "aux" / aux_name,
|
|
41
|
+
aux_data,
|
|
42
|
+
output_type,
|
|
43
|
+
aux_name,
|
|
44
|
+
format_overrides,
|
|
45
|
+
visualization_overrides,
|
|
40
46
|
)
|
|
41
47
|
else:
|
|
42
48
|
_save_slot(
|
|
43
|
-
type_dir / slot_name,
|
|
44
|
-
|
|
49
|
+
type_dir / slot_name,
|
|
50
|
+
data,
|
|
51
|
+
output_type,
|
|
52
|
+
slot_name,
|
|
53
|
+
format_overrides,
|
|
54
|
+
visualization_overrides,
|
|
45
55
|
)
|
|
46
56
|
|
|
47
57
|
|
|
@@ -55,12 +65,16 @@ def _save_slot(
|
|
|
55
65
|
output_type: str,
|
|
56
66
|
leaf_name: str,
|
|
57
67
|
format_overrides: dict[str, str],
|
|
68
|
+
visualization_overrides: dict[str, Any],
|
|
58
69
|
) -> None:
|
|
59
70
|
slot_dir.mkdir(parents=True, exist_ok=True)
|
|
60
71
|
items = _unpack(data)
|
|
72
|
+
visualization = _resolve_visualization(
|
|
73
|
+
output_type, leaf_name, visualization_overrides,
|
|
74
|
+
)
|
|
61
75
|
for idx, item in enumerate(items):
|
|
62
76
|
fmt = _resolve_format(item, output_type, leaf_name, format_overrides)
|
|
63
|
-
_save_item(slot_dir / f"{idx:04d}.{fmt}", item, fmt)
|
|
77
|
+
_save_item(slot_dir / f"{idx:04d}.{fmt}", item, fmt, visualization)
|
|
64
78
|
|
|
65
79
|
|
|
66
80
|
# ---- normalisation -------------------------------------------------------
|
|
@@ -88,7 +102,12 @@ def _prepare(data: Any) -> Any:
|
|
|
88
102
|
|
|
89
103
|
# torch Tensor → numpy, channels-first → channels-last
|
|
90
104
|
if hasattr(data, "detach"):
|
|
91
|
-
|
|
105
|
+
tensor = data.detach().cpu()
|
|
106
|
+
try:
|
|
107
|
+
arr: np.ndarray = tensor.numpy()
|
|
108
|
+
except TypeError:
|
|
109
|
+
# NumPy cannot represent some torch dtypes (for example bfloat16).
|
|
110
|
+
arr = tensor.float().numpy()
|
|
92
111
|
# (C, H, W) → (H, W, C) when C looks like a channel dim
|
|
93
112
|
if (
|
|
94
113
|
arr.ndim == 3
|
|
@@ -119,14 +138,11 @@ def _is_image_like(arr: np.ndarray) -> bool:
|
|
|
119
138
|
return False
|
|
120
139
|
|
|
121
140
|
|
|
122
|
-
def
|
|
123
|
-
item: Any,
|
|
141
|
+
def _resolve_override(
|
|
124
142
|
output_type: str,
|
|
125
143
|
leaf_name: str,
|
|
126
|
-
overrides:
|
|
127
|
-
) ->
|
|
128
|
-
"""Pick save format: check overrides (most-specific first), then infer."""
|
|
129
|
-
# "rgb.pred" > "rgb" > "pred"
|
|
144
|
+
overrides: Mapping[str, Any],
|
|
145
|
+
) -> Any:
|
|
130
146
|
specific = f"{output_type}.{leaf_name}"
|
|
131
147
|
if specific in overrides:
|
|
132
148
|
return overrides[specific]
|
|
@@ -134,6 +150,19 @@ def _resolve_format(
|
|
|
134
150
|
return overrides[output_type]
|
|
135
151
|
if leaf_name in overrides:
|
|
136
152
|
return overrides[leaf_name]
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _resolve_format(
|
|
157
|
+
item: Any,
|
|
158
|
+
output_type: str,
|
|
159
|
+
leaf_name: str,
|
|
160
|
+
overrides: dict[str, str],
|
|
161
|
+
) -> str:
|
|
162
|
+
"""Pick save format: check overrides (most-specific first), then infer."""
|
|
163
|
+
override = _resolve_override(output_type, leaf_name, overrides)
|
|
164
|
+
if override is not None:
|
|
165
|
+
return str(override)
|
|
137
166
|
|
|
138
167
|
# PIL Image
|
|
139
168
|
try:
|
|
@@ -148,11 +177,34 @@ def _resolve_format(
|
|
|
148
177
|
return "npy"
|
|
149
178
|
|
|
150
179
|
|
|
180
|
+
def _resolve_visualization(
|
|
181
|
+
output_type: str,
|
|
182
|
+
leaf_name: str,
|
|
183
|
+
overrides: Mapping[str, Any],
|
|
184
|
+
) -> dict[str, Any] | None:
|
|
185
|
+
override = _resolve_override(output_type, leaf_name, overrides)
|
|
186
|
+
if override is None:
|
|
187
|
+
return None
|
|
188
|
+
if isinstance(override, str):
|
|
189
|
+
return {"mode": override}
|
|
190
|
+
if isinstance(override, Mapping):
|
|
191
|
+
return dict(override)
|
|
192
|
+
raise TypeError(
|
|
193
|
+
"Visualization overrides must be strings or mappings, "
|
|
194
|
+
f"got {type(override)!r}",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
151
198
|
# ---- writers -------------------------------------------------------------
|
|
152
199
|
|
|
153
|
-
def _save_item(
|
|
200
|
+
def _save_item(
|
|
201
|
+
path: Path,
|
|
202
|
+
item: Any,
|
|
203
|
+
fmt: str,
|
|
204
|
+
visualization: dict[str, Any] | None = None,
|
|
205
|
+
) -> None:
|
|
154
206
|
if fmt == "png":
|
|
155
|
-
_save_png(path, item)
|
|
207
|
+
_save_png(path, item, visualization)
|
|
156
208
|
elif fmt == "npy":
|
|
157
209
|
np.save(str(path), item if isinstance(item, np.ndarray) else np.asarray(item))
|
|
158
210
|
elif fmt == "npz":
|
|
@@ -164,7 +216,11 @@ def _save_item(path: Path, item: Any, fmt: str) -> None:
|
|
|
164
216
|
raise ValueError(f"Unsupported format: {fmt!r}")
|
|
165
217
|
|
|
166
218
|
|
|
167
|
-
def _save_png(
|
|
219
|
+
def _save_png(
|
|
220
|
+
path: Path,
|
|
221
|
+
item: Any,
|
|
222
|
+
visualization: dict[str, Any] | None = None,
|
|
223
|
+
) -> None:
|
|
168
224
|
from PIL import Image
|
|
169
225
|
|
|
170
226
|
# PIL Image — save directly
|
|
@@ -174,10 +230,9 @@ def _save_png(path: Path, item: Any) -> None:
|
|
|
174
230
|
|
|
175
231
|
arr: np.ndarray = item
|
|
176
232
|
|
|
177
|
-
# float →
|
|
233
|
+
# float → visualization policy → uint8
|
|
178
234
|
if np.issubdtype(arr.dtype, np.floating):
|
|
179
|
-
arr =
|
|
180
|
-
arr = (arr * 255).astype(np.uint8)
|
|
235
|
+
arr = _render_float_png(arr, visualization)
|
|
181
236
|
elif arr.dtype != np.uint8:
|
|
182
237
|
arr = arr.astype(np.uint8)
|
|
183
238
|
|
|
@@ -192,3 +247,75 @@ def _save_png(path: Path, item: Any) -> None:
|
|
|
192
247
|
Image.fromarray(plane, mode=mode).save(str(path))
|
|
193
248
|
else:
|
|
194
249
|
raise ValueError(f"Cannot save {arr.ndim}D array as PNG")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _render_float_png(
|
|
253
|
+
arr: np.ndarray,
|
|
254
|
+
visualization: dict[str, Any] | None,
|
|
255
|
+
) -> np.ndarray:
|
|
256
|
+
policy = {"mode": "unit_range"}
|
|
257
|
+
if visualization is not None:
|
|
258
|
+
policy.update(visualization)
|
|
259
|
+
|
|
260
|
+
mode = str(policy.get("mode", "unit_range"))
|
|
261
|
+
finite = np.isfinite(arr)
|
|
262
|
+
|
|
263
|
+
if mode == "unit_range":
|
|
264
|
+
scaled = np.asarray(arr, dtype=np.float32)
|
|
265
|
+
elif mode == "minmax":
|
|
266
|
+
scaled = _normalize_minmax(arr, finite)
|
|
267
|
+
elif mode == "percentile":
|
|
268
|
+
pmin = float(policy.get("pmin", 1.0))
|
|
269
|
+
pmax = float(policy.get("pmax", 99.0))
|
|
270
|
+
if pmax <= pmin:
|
|
271
|
+
raise ValueError(
|
|
272
|
+
"Percentile visualization requires pmax > pmin, "
|
|
273
|
+
f"got pmin={pmin} pmax={pmax}",
|
|
274
|
+
)
|
|
275
|
+
scaled = _normalize_percentile(arr, finite, pmin, pmax)
|
|
276
|
+
elif mode == "fixed_range":
|
|
277
|
+
vmin = float(policy.get("vmin", 0.0))
|
|
278
|
+
vmax = float(policy.get("vmax", 1.0))
|
|
279
|
+
if vmax <= vmin:
|
|
280
|
+
raise ValueError(
|
|
281
|
+
"Fixed-range visualization requires vmax > vmin, "
|
|
282
|
+
f"got vmin={vmin} vmax={vmax}",
|
|
283
|
+
)
|
|
284
|
+
scaled = (np.asarray(arr, dtype=np.float32) - vmin) / (vmax - vmin)
|
|
285
|
+
else:
|
|
286
|
+
raise ValueError(f"Unsupported visualization mode: {mode!r}")
|
|
287
|
+
|
|
288
|
+
scaled = np.nan_to_num(scaled, nan=0.0, posinf=1.0, neginf=0.0)
|
|
289
|
+
if bool(policy.get("invert", False)):
|
|
290
|
+
scaled = 1.0 - scaled
|
|
291
|
+
scaled = np.clip(scaled, 0.0, 1.0)
|
|
292
|
+
return (scaled * 255).astype(np.uint8)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _normalize_minmax(arr: np.ndarray, finite: np.ndarray) -> np.ndarray:
|
|
296
|
+
values = arr[finite]
|
|
297
|
+
if values.size == 0:
|
|
298
|
+
return np.zeros_like(arr, dtype=np.float32)
|
|
299
|
+
|
|
300
|
+
vmin = float(values.min())
|
|
301
|
+
vmax = float(values.max())
|
|
302
|
+
if vmax <= vmin:
|
|
303
|
+
return np.zeros_like(arr, dtype=np.float32)
|
|
304
|
+
return (np.asarray(arr, dtype=np.float32) - vmin) / (vmax - vmin)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _normalize_percentile(
|
|
308
|
+
arr: np.ndarray,
|
|
309
|
+
finite: np.ndarray,
|
|
310
|
+
pmin: float,
|
|
311
|
+
pmax: float,
|
|
312
|
+
) -> np.ndarray:
|
|
313
|
+
values = arr[finite]
|
|
314
|
+
if values.size == 0:
|
|
315
|
+
return np.zeros_like(arr, dtype=np.float32)
|
|
316
|
+
|
|
317
|
+
lo = float(np.percentile(values, pmin))
|
|
318
|
+
hi = float(np.percentile(values, pmax))
|
|
319
|
+
if hi <= lo:
|
|
320
|
+
return np.zeros_like(arr, dtype=np.float32)
|
|
321
|
+
return (np.asarray(arr, dtype=np.float32) - lo) / (hi - lo)
|
|
@@ -38,6 +38,7 @@ class Run:
|
|
|
38
38
|
config: Any = None,
|
|
39
39
|
meta: dict | None = None,
|
|
40
40
|
output_formats: dict[str, str] | None = None,
|
|
41
|
+
output_visualization: dict[str, Any] | None = None,
|
|
41
42
|
gpu_stats_every: int = 100,
|
|
42
43
|
run_id: str | None = None,
|
|
43
44
|
datasets: dict[str, Any] | None = None,
|
|
@@ -73,6 +74,7 @@ class Run:
|
|
|
73
74
|
|
|
74
75
|
self.checkpoint_dir: Path | None = None
|
|
75
76
|
self._output_formats: dict[str, str] = output_formats or {}
|
|
77
|
+
self._output_visualization: dict[str, Any] = output_visualization or {}
|
|
76
78
|
self._start_time = time.time()
|
|
77
79
|
self._finished = False
|
|
78
80
|
self._gpu_handle: Any | None = None
|
|
@@ -305,7 +307,11 @@ class Run:
|
|
|
305
307
|
if slots is None:
|
|
306
308
|
continue
|
|
307
309
|
save_output_tree(
|
|
308
|
-
base / output_type,
|
|
310
|
+
base / output_type,
|
|
311
|
+
slots,
|
|
312
|
+
self._output_formats,
|
|
313
|
+
self._output_visualization,
|
|
314
|
+
output_type,
|
|
309
315
|
)
|
|
310
316
|
return base
|
|
311
317
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "euler-train"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.1.0"
|
|
8
8
|
description = "Lightweight file-based experiment logger for PyTorch"
|
|
9
9
|
requires-python = ">=3.9"
|
|
10
10
|
dependencies = ["numpy", "euler-dataset-contract"]
|
|
@@ -1168,6 +1168,61 @@ class TestSaveOutputsOverrides:
|
|
|
1168
1168
|
run.finish()
|
|
1169
1169
|
|
|
1170
1170
|
|
|
1171
|
+
class TestSaveOutputsVisualization:
|
|
1172
|
+
def test_fixed_range_visualization_for_single_channel_png(self, tmp_path):
|
|
1173
|
+
run = euler_train.init(
|
|
1174
|
+
dir=str(tmp_path / "r"),
|
|
1175
|
+
config={},
|
|
1176
|
+
output_visualization={
|
|
1177
|
+
"depth": {"mode": "fixed_range", "vmin": 0.0, "vmax": 10.0},
|
|
1178
|
+
},
|
|
1179
|
+
)
|
|
1180
|
+
depth = np.linspace(0.0, 10.0, 16, dtype=np.float32).reshape(4, 4, 1)
|
|
1181
|
+
run.save_outputs(epoch=0, step=0, depth=dict(pred=depth))
|
|
1182
|
+
|
|
1183
|
+
f = list((run.dir / "outputs" / "epoch_0_step_0" / "depth" / "pred").iterdir())[0]
|
|
1184
|
+
img = np.array(Image.open(f))
|
|
1185
|
+
assert f.suffix == ".png"
|
|
1186
|
+
assert img.min() == 0
|
|
1187
|
+
assert img.max() == 255
|
|
1188
|
+
run.finish()
|
|
1189
|
+
|
|
1190
|
+
def test_visualization_override_specific_dotted_key(self, tmp_path):
|
|
1191
|
+
run = euler_train.init(
|
|
1192
|
+
dir=str(tmp_path / "r"),
|
|
1193
|
+
config={},
|
|
1194
|
+
output_visualization={
|
|
1195
|
+
"depth.pred": {"mode": "fixed_range", "vmin": 5.0, "vmax": 15.0},
|
|
1196
|
+
},
|
|
1197
|
+
)
|
|
1198
|
+
depth = np.linspace(5.0, 15.0, 16, dtype=np.float32).reshape(4, 4, 1)
|
|
1199
|
+
run.save_outputs(epoch=0, step=0, depth=dict(pred=depth, gt=depth))
|
|
1200
|
+
|
|
1201
|
+
base = run.dir / "outputs" / "epoch_0_step_0" / "depth"
|
|
1202
|
+
pred_f = list((base / "pred").iterdir())[0]
|
|
1203
|
+
gt_f = list((base / "gt").iterdir())[0]
|
|
1204
|
+
pred_img = np.array(Image.open(pred_f))
|
|
1205
|
+
gt_img = np.array(Image.open(gt_f))
|
|
1206
|
+
assert pred_img.min() == 0
|
|
1207
|
+
assert pred_img.max() == 255
|
|
1208
|
+
assert gt_img.min() == 255
|
|
1209
|
+
assert gt_img.max() == 255
|
|
1210
|
+
run.finish()
|
|
1211
|
+
|
|
1212
|
+
def test_torch_bfloat16_single_channel_png_promoted_to_float32(self, tmp_path):
|
|
1213
|
+
run = euler_train.init(dir=str(tmp_path / "r"), config={})
|
|
1214
|
+
t = torch.linspace(0, 1, steps=16, dtype=torch.bfloat16).reshape(4, 4, 1)
|
|
1215
|
+
run.save_outputs(epoch=0, step=0, confidence=dict(pred=t))
|
|
1216
|
+
|
|
1217
|
+
f = list((run.dir / "outputs" / "epoch_0_step_0" / "confidence" / "pred").iterdir())[0]
|
|
1218
|
+
img = np.array(Image.open(f))
|
|
1219
|
+
assert f.suffix == ".png"
|
|
1220
|
+
assert img.dtype == np.uint8
|
|
1221
|
+
assert img.min() == 0
|
|
1222
|
+
assert img.max() >= 254
|
|
1223
|
+
run.finish()
|
|
1224
|
+
|
|
1225
|
+
|
|
1171
1226
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
1172
1227
|
# save_outputs — batches, lists, aux, PIL, torch
|
|
1173
1228
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|