Skip to content
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

ImageOps.autocontrast: add mask parameter #4843

Merged
merged 13 commits into from Aug 22, 2020
Merged
52 changes: 51 additions & 1 deletion Tests/test_imageops.py
@@ -1,6 +1,6 @@
import pytest

from PIL import Image, ImageOps, features
from PIL import Image, ImageDraw, ImageOps, ImageStat, features

from .helper import (
assert_image_equal,
Expand All @@ -25,7 +25,9 @@ def test_sanity():
ImageOps.autocontrast(hopper("RGB"))

ImageOps.autocontrast(hopper("L"), cutoff=10)
ImageOps.autocontrast(hopper("L"), cutoff=(2, 10))
ImageOps.autocontrast(hopper("L"), ignore=[0, 255])
ImageOps.autocontrast(hopper("L"), mask=hopper("L"))

ImageOps.colorize(hopper("L"), (0, 0, 0), (255, 255, 255))
ImageOps.colorize(hopper("L"), "black", "white")
Expand Down Expand Up @@ -312,3 +314,51 @@ def autocontrast(cutoff):

assert autocontrast(10) == autocontrast((10, 10))
assert autocontrast(10) != autocontrast((1, 10))


def test_autocontrast_mask_toy_input():
# Test the mask argument of autocontrast
with Image.open("Tests/images/bw_gradient.png") as img:

rect_mask = Image.new("L", img.size, 0)
draw = ImageDraw.Draw(rect_mask)
x0 = img.size[0] // 4
y0 = img.size[1] // 4
x1 = 3 * img.size[0] // 4
y1 = 3 * img.size[1] // 4
draw.rectangle((x0, y0, x1, y1), fill=255)

result = ImageOps.autocontrast(img, mask=rect_mask)
result_nomask = ImageOps.autocontrast(img)

assert result != result_nomask
assert ImageStat.Stat(result, mask=rect_mask).median == [127]
assert ImageStat.Stat(result_nomask).median == [128]


def test_auto_contrast_mask_real_input():
# Test the autocontrast with a rectangular mask
hugovk marked this conversation as resolved.
Show resolved Hide resolved
with Image.open("Tests/images/iptc.jpg") as img:

rect_mask = Image.new("L", img.size, 0)
draw = ImageDraw.Draw(rect_mask)
x0, y0 = img.size[0] // 2, img.size[1] // 2
x1, y1 = img.size[0] - 40, img.size[1]
draw.rectangle((x0, y0, x1, y1), fill=255)

result = ImageOps.autocontrast(img, mask=rect_mask)
result_nomask = ImageOps.autocontrast(img)

assert result_nomask != result
Copy link
Member

Choose a reason for hiding this comment

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

Same comment here about testing for correctness.

assert_tuple_approx_equal(
ImageStat.Stat(result, mask=rect_mask).median,
[195, 202, 184],
threshold=2,
msg="autocontrast with mask pixel incorrect",
)
assert_tuple_approx_equal(
ImageStat.Stat(result_nomask).median,
[119, 106, 79],
threshold=2,
msg="autocontrast without mask pixel incorrect",
)
9 changes: 6 additions & 3 deletions src/PIL/ImageOps.py
Expand Up @@ -61,10 +61,10 @@ def _lut(image, lut):
# actions


def autocontrast(image, cutoff=0, ignore=None):
def autocontrast(image, cutoff=0, ignore=None, mask=None):
"""
Maximize (normalize) image contrast. This function calculates a
histogram of the input image, removes **cutoff** percent of the
histogram of the input image (or mask region), removes **cutoff** percent of the
lightest and darkest pixels from the histogram, and remaps the image
so that the darkest pixel becomes black (0), and the lightest
becomes white (255).
Expand All @@ -74,9 +74,12 @@ def autocontrast(image, cutoff=0, ignore=None):
high ends. Either a tuple of (low, high), or a single
number for both.
:param ignore: The background pixel value (use None for no background).
:param mask: Histogram used in contrast operation is computed using pixels
within the mask. If no mask is given the entire image is used
for histogram computation.
:return: An image.
"""
histogram = image.histogram()
histogram = image.histogram(mask)
lut = []
for layer in range(0, len(histogram), 256):
h = histogram[layer : layer + 256]
Expand Down