From 0002e18c74c66791b0b7a14fc4903fafd52ce8ec Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 9 Aug 2017 01:58:22 +0300 Subject: [PATCH 1/7] New Image.getchannel method --- PIL/Image.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/PIL/Image.py b/PIL/Image.py index 49c9f6ab2..f67009452 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1959,6 +1959,22 @@ class Image(object): ims.append(self._new(self.im.getband(i))) return tuple(ims) + def getchannel(self, channel): + """ + Returns an image contained single channel of the source image. + + :param channel: What channel to return. Could be index + (0 for "R" channel of "RGB") or channel name + ("A" for alpha channel of "RGBA"). + :returns: An image in "L" mode. + """ + + if isStringType(channel) and len(channel) == 1: + if channel in self.im.mode: + channel = self.im.mode.index(channel) + + return self._new(self.im.getband(channel)) + def tell(self): """ Returns the current frame number. See :py:meth:`~PIL.Image.Image.seek`. From 349e300d7b0bcdca0e4fb4d9edb2d41fb5ed3d35 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 9 Aug 2017 02:36:07 +0300 Subject: [PATCH 2/7] use getchannel where is possible --- PIL/ImageEnhance.py | 6 +++--- Tests/test_file_png.py | 12 ++++++------ Tests/test_format_hsv.py | 27 +++++++++++++++------------ Tests/test_image_convert.py | 4 ++-- Tests/test_imagecms.py | 5 ++--- Tests/test_imageenhance.py | 2 +- Tests/test_numpy.py | 2 +- 7 files changed, 30 insertions(+), 28 deletions(-) diff --git a/PIL/ImageEnhance.py b/PIL/ImageEnhance.py index b38f406a3..11c9c3a06 100644 --- a/PIL/ImageEnhance.py +++ b/PIL/ImageEnhance.py @@ -67,7 +67,7 @@ class Contrast(_Enhance): self.degenerate = Image.new("L", image.size, mean).convert(image.mode) if 'A' in image.getbands(): - self.degenerate.putalpha(image.split()[-1]) + self.degenerate.putalpha(image.getchannel('A')) class Brightness(_Enhance): @@ -82,7 +82,7 @@ class Brightness(_Enhance): self.degenerate = Image.new(image.mode, image.size, 0) if 'A' in image.getbands(): - self.degenerate.putalpha(image.split()[-1]) + self.degenerate.putalpha(image.getchannel('A')) class Sharpness(_Enhance): @@ -97,4 +97,4 @@ class Sharpness(_Enhance): self.degenerate = image.filter(ImageFilter.SMOOTH) if 'A' in image.getbands(): - self.degenerate.putalpha(image.split()[-1]) + self.degenerate.putalpha(image.getchannel('A')) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 7dab44333..7ee2c9fb7 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -200,7 +200,7 @@ class TestFilePng(PillowTestCase): self.assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - self.assertEqual(len(im.split()[3].getcolors()), 124) + self.assertEqual(len(im.getchannel('A').getcolors()), 124) def test_load_transparent_rgb(self): test_file = "Tests/images/rgb_trns.png" @@ -212,7 +212,7 @@ class TestFilePng(PillowTestCase): self.assert_image(im, "RGBA", (64, 64)) # image has 876 transparent pixels - self.assertEqual(im.split()[3].getcolors()[0][0], 876) + self.assertEqual(im.getchannel('A').getcolors()[0][0], 876) def test_save_p_transparent_palette(self): in_file = "Tests/images/pil123p.png" @@ -234,7 +234,7 @@ class TestFilePng(PillowTestCase): self.assert_image(im, "RGBA", (162, 150)) # image has 124 unique alpha values - self.assertEqual(len(im.split()[3].getcolors()), 124) + self.assertEqual(len(im.getchannel('A').getcolors()), 124) def test_save_p_single_transparency(self): in_file = "Tests/images/p_trns_single.png" @@ -258,7 +258,7 @@ class TestFilePng(PillowTestCase): self.assertEqual(im.getpixel((31, 31)), (0, 255, 52, 0)) # image has 876 transparent pixels - self.assertEqual(im.split()[3].getcolors()[0][0], 876) + self.assertEqual(im.getchannel('A').getcolors()[0][0], 876) def test_save_p_transparent_black(self): # check if solid black image with full transparency @@ -287,7 +287,7 @@ class TestFilePng(PillowTestCase): # There are 559 transparent pixels. im = im.convert('RGBA') - self.assertEqual(im.split()[3].getcolors()[0][0], 559) + self.assertEqual(im.getchannel('A').getcolors()[0][0], 559) def test_save_rgb_single_transparency(self): in_file = "Tests/images/caption_6_33_22.png" @@ -550,7 +550,7 @@ class TestTruncatedPngPLeaks(PillowTestCase): # linux # man 2 getrusage # ru_maxrss (since Linux 2.6.32) - # This is the maximum resident set size used (in kilobytes). + # This is the maximum resident set size used (in kilobytes). return mem / 1024 # megs def test_leak_load(self): diff --git a/Tests/test_format_hsv.py b/Tests/test_format_hsv.py index b965a854f..2cc54c910 100644 --- a/Tests/test_format_hsv.py +++ b/Tests/test_format_hsv.py @@ -103,11 +103,11 @@ class TestFormatHSV(PillowTestCase): # im.split()[0].show() # comparable.split()[0].show() - self.assert_image_similar(im.split()[0], comparable.split()[0], + self.assert_image_similar(im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong") - self.assert_image_similar(im.split()[1], comparable.split()[1], + self.assert_image_similar(im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong") - self.assert_image_similar(im.split()[2], comparable.split()[2], + self.assert_image_similar(im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong") # print(im.getpixel((192, 64))) @@ -120,11 +120,11 @@ class TestFormatHSV(PillowTestCase): # print(im.getpixel((192, 64))) # print(comparable.getpixel((192, 64))) - self.assert_image_similar(im.split()[0], comparable.split()[0], + self.assert_image_similar(im.getchannel(0), comparable.getchannel(0), 3, "R conversion is wrong") - self.assert_image_similar(im.split()[1], comparable.split()[1], + self.assert_image_similar(im.getchannel(1), comparable.getchannel(1), 3, "G conversion is wrong") - self.assert_image_similar(im.split()[2], comparable.split()[2], + self.assert_image_similar(im.getchannel(2), comparable.getchannel(2), 3, "B conversion is wrong") def test_convert(self): @@ -137,11 +137,11 @@ class TestFormatHSV(PillowTestCase): # print(im.split()[0].histogram()) # print(comparable.split()[0].histogram()) - self.assert_image_similar(im.split()[0], comparable.split()[0], + self.assert_image_similar(im.getchannel(0), comparable.getchannel(0), 1, "Hue conversion is wrong") - self.assert_image_similar(im.split()[1], comparable.split()[1], + self.assert_image_similar(im.getchannel(1), comparable.getchannel(1), 1, "Saturation conversion is wrong") - self.assert_image_similar(im.split()[2], comparable.split()[2], + self.assert_image_similar(im.getchannel(2), comparable.getchannel(2), 1, "Value conversion is wrong") def test_hsv_to_rgb(self): @@ -155,11 +155,14 @@ class TestFormatHSV(PillowTestCase): # print([ord(x) for x in target.split()[1].tobytes()[:80]]) # print([ord(x) for x in converted.split()[1].tobytes()[:80]]) - self.assert_image_similar(converted.split()[0], comparable.split()[0], + self.assert_image_similar(converted.getchannel(0), + comparable.getchannel(0), 3, "R conversion is wrong") - self.assert_image_similar(converted.split()[1], comparable.split()[1], + self.assert_image_similar(converted.getchannel(1), + comparable.getchannel(1), 3, "G conversion is wrong") - self.assert_image_similar(converted.split()[2], comparable.split()[2], + self.assert_image_similar(converted.getchannel(2), + comparable.getchannel(2), 3, "B conversion is wrong") diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 5687232f3..f56e2f4ed 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -133,7 +133,7 @@ class TestImageConvert(PillowTestCase): alpha = hopper('L') im.putalpha(alpha) - comparable = im.convert('P').convert('LA').split()[1] + comparable = im.convert('P').convert('LA').getchannel('A') self.assert_image_similar(alpha, comparable, 5) @@ -185,7 +185,7 @@ class TestImageConvert(PillowTestCase): if converted_im.mode == 'RGB': self.assert_image_similar(converted_im, target, 3) else: - self.assert_image_similar(converted_im, target.split()[0], 1) + self.assert_image_similar(converted_im, target.getchannel(0), 1) matrix_convert('RGB') matrix_convert('L') diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index ef30f3e00..aa592c398 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -360,8 +360,7 @@ class TestImageCms(PillowTestCase): return Image.merge(mode, chans) source_image = create_test_image() - preserved_channel_ndx = source_image.getbands().index(preserved_channel) - source_image_aux = source_image.split()[preserved_channel_ndx] + source_image_aux = source_image.getchannel(preserved_channel) # create some transform, it doesn't matter which one source_profile = ImageCms.createProfile("sRGB") @@ -374,7 +373,7 @@ class TestImageCms(PillowTestCase): result_image = source_image else: result_image = ImageCms.applyTransform(source_image, t, inPlace=False) - result_image_aux = result_image.split()[preserved_channel_ndx] + result_image_aux = result_image.getchannel(preserved_channel) self.assert_image_equal(source_image_aux, result_image_aux) diff --git a/Tests/test_imageenhance.py b/Tests/test_imageenhance.py index 7278ec8c1..e9727613b 100644 --- a/Tests/test_imageenhance.py +++ b/Tests/test_imageenhance.py @@ -34,7 +34,7 @@ class TestImageEnhance(PillowTestCase): def _check_alpha(self, im, original, op, amount): self.assertEqual(im.getbands(), original.getbands()) - self.assert_image_equal(im.split()[-1], original.split()[-1], + self.assert_image_equal(im.getchannel('A'), original.getchannel('A'), "Diff on %s: %s" % (op, amount)) def test_alpha(self): diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 8b9208cd6..74825cd41 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -52,7 +52,7 @@ class TestNumpy(PillowTestCase): a = numpy.array([[x]*bands for x in data], dtype=dtype) a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands i = Image.fromarray(a) - if list(i.split()[0].getdata()) != list(range(100)): + if list(i.getchannel(0).getdata()) != list(range(100)): print("data mismatch for", dtype) # print(dtype, list(i.getdata())) return i From 1a7cb317be40c275d35ea975a858bbc81ef3cdd0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Wed, 9 Aug 2017 02:39:53 +0300 Subject: [PATCH 3/7] load image before getting channels --- PIL/Image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PIL/Image.py b/PIL/Image.py index f67009452..db26b9f20 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1968,6 +1968,7 @@ class Image(object): ("A" for alpha channel of "RGBA"). :returns: An image in "L" mode. """ + self.load() if isStringType(channel) and len(channel) == 1: if channel in self.im.mode: From b46b5c4e84ec565af3945572c7849c9ccde089a0 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 12 Aug 2017 01:24:53 +0300 Subject: [PATCH 4/7] release notes autodocs fix docstring note for `Image.split` --- PIL/Image.py | 9 +++++++-- docs/reference/Image.rst | 1 + docs/releasenotes/4.3.0.rst | 9 +++++++++ docs/releasenotes/index.rst | 3 +-- 4 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 docs/releasenotes/4.3.0.rst diff --git a/PIL/Image.py b/PIL/Image.py index db26b9f20..b2111c97c 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1947,6 +1947,9 @@ class Image(object): containing a copy of one of the original bands (red, green, blue). + If you need only one band, :py:meth:`~PIL.Image.Image.getchannel` + method can be more convinient and faster. + :returns: A tuple containing bands. """ @@ -1964,9 +1967,11 @@ class Image(object): Returns an image contained single channel of the source image. :param channel: What channel to return. Could be index - (0 for "R" channel of "RGB") or channel name - ("A" for alpha channel of "RGBA"). + (0 for "R" channel of "RGB") or channel name + ("A" for alpha channel of "RGBA"). :returns: An image in "L" mode. + + .. versionadded:: 4.3.0 """ self.load() diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index e867494b2..8977a4332 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -146,6 +146,7 @@ ITU-R 709, using the D65 luminant) to the CIE XYZ color space: .. automethod:: PIL.Image.Image.seek .. automethod:: PIL.Image.Image.show .. automethod:: PIL.Image.Image.split +.. automethod:: PIL.Image.Image.getchannel .. automethod:: PIL.Image.Image.tell .. automethod:: PIL.Image.Image.thumbnail .. automethod:: PIL.Image.Image.tobitmap diff --git a/docs/releasenotes/4.3.0.rst b/docs/releasenotes/4.3.0.rst new file mode 100644 index 000000000..a356a67a2 --- /dev/null +++ b/docs/releasenotes/4.3.0.rst @@ -0,0 +1,9 @@ +4.3.0 +----- + +Get One Channel From Image +============================ + +New method :py:meth:`PIL.Image.Image.getchannel` added. +It returns single channel by index or name. For example, +``image.getchannel("A")`` will return alpha channel as seperate image. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 27b12face..4ea95f659 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -6,6 +6,7 @@ Release Notes .. toctree:: :maxdepth: 2 + 4.3.0 4.2.1 4.2.0 4.1.1 @@ -21,5 +22,3 @@ Release Notes 3.0.0 2.8.0 2.7.0 - - From a1e2d42ea08091803615377727d7008e182d33ee Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 12 Aug 2017 10:32:42 +0300 Subject: [PATCH 5/7] text fixes --- PIL/Image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index b2111c97c..f23c4e32c 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1948,7 +1948,7 @@ class Image(object): blue). If you need only one band, :py:meth:`~PIL.Image.Image.getchannel` - method can be more convinient and faster. + method can be more convenient and faster. :returns: A tuple containing bands. """ @@ -1964,7 +1964,7 @@ class Image(object): def getchannel(self, channel): """ - Returns an image contained single channel of the source image. + Returns an image containing a single channel of the source image. :param channel: What channel to return. Could be index (0 for "R" channel of "RGB") or channel name From e16ab0ad2ead98205143d06940f79a45300b4f33 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 12 Aug 2017 14:10:39 +0300 Subject: [PATCH 6/7] add tests, fix implementation --- PIL/Image.py | 9 ++++++--- Tests/test_image.py | 24 +++++++++++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index f23c4e32c..4555561a1 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1975,9 +1975,12 @@ class Image(object): """ self.load() - if isStringType(channel) and len(channel) == 1: - if channel in self.im.mode: - channel = self.im.mode.index(channel) + if isStringType(channel): + try: + channel = self.getbands().index(channel) + except ValueError: + raise ValueError( + 'The image has no channel "{}"'.format(channel)) return self._new(self.im.getband(channel)) diff --git a/Tests/test_image.py b/Tests/test_image.py index 1f9c4d798..205c58e43 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -145,14 +145,28 @@ class TestImage(PillowTestCase): self.assertEqual(im.size[1], orig_size[1] + 2*ymargin) def test_getbands(self): - # Arrange + # Assert + self.assertEqual(hopper('RGB').getbands(), ('R', 'G', 'B')) + self.assertEqual(hopper('YCbCr').getbands(), ('Y', 'Cb', 'Cr')) + + def test_getchannel_wrong_params(self): im = hopper() - # Act - bands = im.getbands() + self.assertRaises(ValueError, im.getchannel, -1) + self.assertRaises(ValueError, im.getchannel, 3) + self.assertRaises(ValueError, im.getchannel, 'Z') + self.assertRaises(ValueError, im.getchannel, '1') - # Assert - self.assertEqual(bands, ('R', 'G', 'B')) + def test_getchannel(self): + im = hopper('YCbCr') + Y, Cb, Cr = im.split() + + self.assert_image_equal(Y, im.getchannel(0)) + self.assert_image_equal(Y, im.getchannel('Y')) + self.assert_image_equal(Cb, im.getchannel(1)) + self.assert_image_equal(Cb, im.getchannel('Cb')) + self.assert_image_equal(Cr, im.getchannel(2)) + self.assert_image_equal(Cr, im.getchannel('Cr')) def test_getbbox(self): # Arrange From 56f957b5db110880ce818caa81c3c127fbbbb86f Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 12 Aug 2017 14:22:36 +0300 Subject: [PATCH 7/7] fix number of equals [skipci] --- docs/releasenotes/4.3.0.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasenotes/4.3.0.rst b/docs/releasenotes/4.3.0.rst index a356a67a2..c0a9d3c75 100644 --- a/docs/releasenotes/4.3.0.rst +++ b/docs/releasenotes/4.3.0.rst @@ -2,7 +2,7 @@ ----- Get One Channel From Image -============================ +========================== New method :py:meth:`PIL.Image.Image.getchannel` added. It returns single channel by index or name. For example,