-
Notifications
You must be signed in to change notification settings - Fork 677
Convert torchvision ColorJitter hue to degrees for fn.color_twist #6471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
c9971ca
f61ac18
aedaeb4
0f78dcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -208,12 +208,16 @@ def __init__( | |
| if isinstance(hue, (int, float)): | ||
| self.hue = (-float(hue), float(hue)) | ||
|
|
||
| # torchvision expresses hue as a fraction of a full turn (|hue| <= 0.5), while | ||
| # fn.color_twist takes the hue delta in degrees. | ||
| self._hue_degrees = tuple(float(h) * 360.0 for h in self.hue) | ||
|
|
||
| def _kernel(self, data_input): | ||
| """ | ||
| Performs the color jitter using the ``fn.color_twist`` operator. | ||
| """ | ||
| brightness, contrast, saturation, hue = _get_BrightnessContrastSaturationHue( | ||
| self.brightness, self.contrast, self.saturation, self.hue, fn.random.uniform | ||
| self.brightness, self.contrast, self.saturation, self._hue_degrees, fn.random.uniform | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Minor] Now that |
||
| ) | ||
|
|
||
| data_input = fn.color_twist( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
|
|
||
| import os | ||
|
|
||
| import numpy as np | ||
| from nose2.tools import params, cartesian_params | ||
| from nose_utils import assert_raises | ||
| from PIL import Image | ||
|
|
@@ -149,6 +150,35 @@ def test_colorjitter_images(cj_params, device): | |
| _ = cj(img) | ||
|
|
||
|
|
||
| def median_hue_shift(before: Image.Image, after: Image.Image) -> float: | ||
| """Median hue rotation from `before` to `after`, in degrees, over the colorful pixels.""" | ||
| hue_before, saturation, _ = before.convert("HSV").split() | ||
| hue_after, _, _ = after.convert("HSV").split() | ||
| to_degrees = 360.0 / 256.0 | ||
| hue_before = np.asarray(hue_before, dtype=np.float64) * to_degrees | ||
| hue_after = np.asarray(hue_after, dtype=np.float64) * to_degrees | ||
| # hue is meaningless for near-gray pixels | ||
| colorful = np.asarray(saturation) > 32 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Minor] If |
||
| shift = (hue_after - hue_before + 180.0) % 360.0 - 180.0 | ||
| return float(np.median(shift[colorful])) | ||
|
|
||
|
|
||
| @cartesian_params((0.05, 0.1, -0.1), ("cpu", "gpu")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new regression test covers both devices but only fixed interior hue values on the existing JPEG inputs. Add the required ±0.5 boundaries, empty input, and explicit Rule Used: New operator tests must cover: empty input, single... (source) Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| def test_colorjitter_hue_rotation(hue, device): | ||
| # torchvision expresses hue as a fraction of a full turn, fn.color_twist takes degrees. | ||
| # The tolerance covers DALI's linear YIQ approximation of the hue rotation. | ||
| cj = Compose([ColorJitter(hue=(hue, hue), device=device)]) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Moderate] Every case uses Worth adding one case with a genuine range, e.g. |
||
|
|
||
| for fn in test_files: | ||
| img = Image.open(fn).convert("RGB") | ||
| expected = median_hue_shift(img, transforms.functional.adjust_hue(img, hue)) | ||
| actual = median_hue_shift(img, cj(img)) | ||
| assert abs(actual - expected) < 15.0, ( | ||
| f"hue={hue} rotated by {actual:.2f} degrees, torchvision rotates by " | ||
| f"{expected:.2f} degrees: {fn}" | ||
| ) | ||
|
|
||
|
|
||
| """ | ||
| TODO (https://github.com/NVIDIA/DALI/issues/DALI-4656): | ||
| DALI ColorJitter does not currently work on CHW layout | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Minor] The fix itself is right (
color_twistdocuments hue as degrees,h_rad = hue * M_PI / 180), but caching the converted value in a second attribute duplicates state that can silently go stale: anything that reassignsself.hueafter construction (subclass, test helper, user code poking at the public attribute) will keep using the__init__-time_hue_degrees. Since_kernelis the only consumer, consider dropping the extra attribute and converting at the point of use:Same comment, one attribute, and
self.huestays the single source of truth.