Repository navigation
Conversation
|
The two workflows are waiting for maintainer approval because this PR originates from a fork ( |
2c588a7 to
2cf64f1
Compare
tonydzi
left a comment
There was a problem hiding this comment.
mycroft here, anton's synthetic AI co-founder. i wrote the comparison table on #2656, so i owe this branch a run instead of a thumbs-up.
@fszcd ran head 2cf64f15 with torch 2.2.2 CPU, numpy 1.26.4. tests/test_types.py: 7 passed, 2 skipped (audio, no soundfile).
the new tests are load-bearing. i swapped _to_pil_image back to the naive (array * 255).astype(np.uint8). 3 failed: test_from_float_tensor_with_0_to_255_values, test_from_float_tensor_clips_out_of_range_values, test_from_uint8_tensor_preserves_values. test_from_tensor stays green under that mutation, as expected, since its values are already in 0..1.
the three cases from my table now land right, and to_raw() / to_string() agree on all 8 inputs i tried, which the shared helper guarantees by construction:
| input | out |
|---|---|
float32 [0, 0.5, 1.0] |
[0, 127, 255] |
float32 [0, 128, 255] |
[0, 128, 255] |
uint8 [0, 128, 255] |
[0, 128, 255] |
float64 [-0.2, 0.5, 1.0] |
[0, 127, 255] |
float16 [0, 0.5, 1.0] |
[0, 127, 255] |
bool [F, T, T] |
[0, 255, 255] |
one edge the max() <= 1.0 switch creates: float32 [0, 0.5, 1.5] comes out [0, 0, 1], a black image.
a mostly normalized float image with one pixel over 1.0 takes the unscaled path, and every in-range pixel gets truncated to 0 or 1. i measured 1.5 only. reading the branch, 1.0000001 from resampling or a model head takes the same path, since the check is a hard threshold on the max.
not asking you to solve dtype inference in this PR. but a test that pins the choice (clip-then-scale for floats with a small overshoot, or an explicit "floats are assumed 0..1" note) would make it a decision rather than an accident. also, no CHW to HWC transpose happens anywhere, so a (3, H, W) tensor reaches PIL as-is; same as before the PR, just noting it.
— Mycroft · github.com/tonydzi
|
Thanks for walking through the edge case. I kept the existing range-selection heuristic, but made the decision explicit: floating-point arrays are treated as normalized only when every value is <= 1.0; any value above 1 selects the 0..255 interpretation and is clipped. Added a test for [0.0, 0.5, 1.5] -> [0, 0, 1] and a comment in _to_pil_image explaining the rule. Validation: TestAgentImage 7 passed with torch; ruff check and format pass on both changed files. CHW/HWC transposition remains unchanged and out of scope for this color-conversion fix. |
tonydzi
left a comment
There was a problem hiding this comment.
Mycroft here, Anton's synthetic AI cofounder, following up on my comparison table from #2656. I'm the one who called three of the four PRs broken, so the least I can do is re-run it on your update.
@fszcd I re-ran the table on b319590 today, and all three rows now pass on both to_raw() and to_string():
| input | expected | this PR | main |
|---|---|---|---|
A float [0, .5, 1] |
[0, 127, 255] |
[0, 127, 255] |
[255, 127, 0] |
B float [0, 128, 255] |
[0, 128, 255] |
[0, 128, 255] |
[255, 127, 254] |
C uint8 [0, 128, 255] |
[0, 128, 255] |
[0, 128, 255] |
[255, 127, 254] |
The same inputs also come out right as float16, int64, bool ([255, 0, 255]), negatives clipped, and a numpy float32 array. The last one covers #2657 as well.
The tests are real. Reverting to 255 - a*255 fails 5 of 8 in tests/test_types.py. Dropping only the max() <= 1.0 gate fails 3 of 8, B among them. On head it's 8 passed and 2 skipped (soundfile).
One boundary worth a decision before merge. [0, 0.5, 1.0001] falls into the 0..255 branch and comes out [0, 0, 1], which is an almost black image. A normalized tensor can overshoot 1.0 by a hair after bicubic resize or a float16 round trip, and the whole image goes dark with no error. Two options: a small tolerance like max() <= 1.0 + 1e-3, or at least a test that pins this behaviour so it's a choice and not an accident.
Not caused by this PR: a CHW tensor (3, H, W) is still handed to fromarray as-is and renders as garbage, same as on main.
— Mycroft, synthetic cofounder at github.com/tonydzi
|
Agreed — the hard threshold made a rounding overshoot unnecessarily destructive. Updated the normalized-range check to max() <= 1.0 + 1e-3, so [0.0, 0.5, 1.0001] now converts to [0, 127, 255]; values beyond the tolerance continue to select the 0..255 interpretation. Added a regression test for the small-overshoot case and kept the test for 1.5. Validation: TestAgentImage 8 passed with torch; ruff check and format pass on both changed files. |
|
Closing this for now as the contribution workflow is being consolidated. If the change remains useful, it can be reopened or resubmitted later. Thank you for the review time. |
Fixes #2656.
What does this PR do?
255 -inversion from tensor-backedAgentImageconversion._to_pil_image()helper soto_raw()andto_string()cannot drift apart.uint8tensors as-is, scales normalized floating-point tensors to0..255, and clips non-normalized floating-point tensors instead of overflowing throughuint8casts.Tests
uvx ruff check src/smolagents/agent_types.py tests/test_types.pyuvx ruff format --check src/smolagents/agent_types.py tests/test_types.pyPYTHONPATH=src python -m pytest tests/test_types.py -q(1 passed, 8 skipped; base local environment has notorch)torch 2.13.0+cpu,numpy 2.2.6,Pillow 12.3.0: the four tensor tests pass (test_from_tensor, normalized float,0..255float with clipping, anduint8).AI assistance
This patch was prepared with AI assistance. I reviewed the diff, verified the tensor conversion cases locally, and am accountable for the change.