Skip to content

Keep achromatic HSL gradients finite - #132

Open
sylvesterkaczmarek wants to merge 3 commits into
google-deepmind:masterfrom
sylvesterkaczmarek:fix/achromatic-hsl-gradients
Open

sylvesterkaczmarek wants to merge 3 commits into
google-deepmind:masterfrom
sylvesterkaczmarek:fix/achromatic-hsl-gradients

Conversation

@sylvesterkaczmarek

@sylvesterkaczmarek sylvesterkaczmarek commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Problem

rgb_to_hsl masks achromatic hue/saturation outputs after dividing by the zero colour range. The forward value for a grey pixel is correct, but reverse differentiation produces NaNs, including in the lightness component. Low-precision black/white pixels also need a safe denominator for the inactive saturation branch.

Change

Replace inactive denominators before division, preserving the existing achromatic output branches and chromatic formula. This follows the JAX guidance on masked undefined operations.

Validation

  • Earlier full colour-conversion run: 171 passed, 40 skipped (JAX 0.7.0, TensorFlow 2.20.0).
  • Current rebased head: 9 HSL regressions and 20 adjacent conversion tests passed; 5 skipped (JAX 0.12.0, TensorFlow 2.21.0). Tests now live in dm_pix/_src/color_conversion_test.py. Unchanged production code fails eight cases and passes the chromatic finite-difference control.
  • Coverage includes black/grey/white, float16/bfloat16/float32, eager/JIT execution, forward/reverse differentiation along the greyscale path, mixed images, and chromatic gradients compared with independent colorsys finite differences.
  • Python syntax and git diff --check pass on the current head.

Hue is mathematically undefined on the achromatic axis. The tests preserve the existing constant hue/saturation branches and verify the lightness derivative along the greyscale path; they do not assert a unique full colour-space derivative there.

Python 3.12.14 on macOS arm64 CPU. GPU/TPU and the complete image-processing suite were not run. Channel-axis handling in #125 is separate and unchanged. No epsilon, colour convention, dependency, or public API changes.

Reproduced against ffb65074b39b67e1a686d1348f2d461c7ff3ab45.

Comment thread dm_pix/_src/hsl_gradients_test.py Outdated
import numpy as np


class HslGradientsTest(parameterized.TestCase):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move these tests under color_conversion_test.py. We already have a color transformation test, so you can put them there, thanks!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the tests into color_conversion_test.py. All 9 new cases and 20 adjacent tests pass on current master.

Comment thread dm_pix/_src/hsl_gradients_test.py Outdated
np.testing.assert_array_equal(grey(x), np.array([0.0, 0.0, level]))
np.testing.assert_allclose(reverse(x), [0.0, 0.0, 1.0], atol=1e-3)
np.testing.assert_allclose(forward(x), [0.0, 0.0, 1.0], atol=1e-3)
jacobian = jax.jacrev(color_conversion.rgb_to_hsl)(jnp.repeat(x[None], 3))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-prepare this outside the loop, so we can clearly see an arrange, act, and assert pattern in each test. Check this also for other tests too, thanks!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RGB Jacobian and its JIT wrapper are now prepared once before the greyscale-level loop.

@sylvesterkaczmarek
sylvesterkaczmarek force-pushed the fix/achromatic-hsl-gradients branch from 378e83c to 52a5c62 Compare October 19, 2026 15:50

@claudiofantacci claudiofantacci left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@claudiofantacci claudiofantacci added the bug Something isn't working label Oct 24, 2026
Comment thread dm_pix/_src/color_conversion_test.py Outdated
def test_chromatic_gradients_match_finite_differences(self):
x = jnp.array([0.2, 0.6, 0.9])
numerical = np.zeros((3, 3))
x64 = np.asarray(x).astype(np.float64)

@pabloduque0 pabloduque0 Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Do this one directly in the line above.

Comment thread dm_pix/_src/color_conversion_test.py Outdated
np.testing.assert_array_equal(jacobian[:2], np.zeros((2, 3)))

@parameterized.parameters(False, True)
def test_mixed_image_values_and_gradients(self, compiled):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Lets leave a blank line between the arrange, act and assert part of the test.

@pabloduque0 pabloduque0 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a couple nits, otherwise LGTM

Signed-off-by: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com>
@sylvesterkaczmarek

Copy link
Copy Markdown
Contributor Author

Thanks for the review, Pablo. I addressed both nits in 73f8b59: the float64 setup is now in one line, and the mixed-image test separates arrange, act and assert. The 9 focused HSL-gradient tests still pass on CPU.

copybara-service Bot pushed a commit that referenced this pull request Oct 27, 2026
@claudiofantacci

Copy link
Copy Markdown
Collaborator

@sylvesterkaczmarek something went wrong with merging back some changes I did internally with yours. I guess the code was at an old commit, sorry about that. Can you please look at the current code again? I'll then redo a review, see internally what happens and hopefully we'll close the loop on this PR.

@claudiofantacci
claudiofantacci self-requested a review October 27, 2026 23:02
@sylvesterkaczmarek

Copy link
Copy Markdown
Contributor Author

Thanks for flagging this, Claudio. I checked current master against the PR branch: the HSL fix and regressions are already on master via 99ad28a. My sync inadvertently removed x from the finite-difference test, which explains the Python CI NameError. I've restored it in 2881e23 (syntax and diff checks pass). Happy to close this as already integrated if that suits your internal workflow. Appreciate your help getting this across the line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants