Download PIL/ImageFilter.py from salmankhanpm/build-tools: direct link, hf CLI and curl.
- Browser
- Download file 18.7 kB
-
https://huggingface.co/salmankhanpm/build-tools/resolve/main/PIL/ImageFilter.py
- Command line
-
hf download hf://salmankhanpm/build-tools/PIL/ImageFilter.py
-
curl -L -o ImageFilter.py https://huggingface.co/salmankhanpm/build-tools/resolve/main/PIL/ImageFilter.py
18.7 kB
| # | |
| # The Python Imaging Library. | |
| # $Id$ | |
| # | |
| # standard filters | |
| # | |
| # History: | |
| # 1995-11-27 fl Created | |
| # 2002-06-08 fl Added rank and mode filters | |
| # 2003-09-15 fl Fixed rank calculation in rank filter; added expand call | |
| # | |
| # Copyright (c) 1997-2003 by Secret Labs AB. | |
| # Copyright (c) 1995-2002 by Fredrik Lundh. | |
| # | |
| # See the README file for information on usage and redistribution. | |
| # | |
| from __future__ import annotations | |
| import abc | |
| import functools | |
| from collections.abc import Sequence | |
| from typing import cast | |
| TYPE_CHECKING = False | |
| if TYPE_CHECKING: | |
| from collections.abc import Callable | |
| from types import ModuleType | |
| from typing import Any | |
| from . import _imaging | |
| from ._typing import NumpyArray | |
| class Filter(abc.ABC): | |
| def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: | |
| pass | |
| class MultibandFilter(Filter): | |
| pass | |
| class BuiltinFilter(MultibandFilter): | |
| filterargs: tuple[Any, ...] | |
| def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: | |
| if image.mode == "P": | |
| msg = "cannot filter palette images" | |
| raise ValueError(msg) | |
| return image.filter(*self.filterargs) | |
| class Kernel(BuiltinFilter): | |
| """ | |
| Create a convolution kernel. This only supports 3x3 and 5x5 integer and floating | |
| point kernels. | |
| Kernels can only be applied to "L" and "RGB" images. | |
| :param size: Kernel size, given as (width, height). This must be (3,3) or (5,5). | |
| :param kernel: A sequence containing kernel weights. The kernel will be flipped | |
| vertically before being applied to the image. | |
| :param scale: Scale factor. If given, the result for each pixel is divided by this | |
| value. The default is the sum of the kernel weights. | |
| :param offset: Offset. If given, this value is added to the result, after it has | |
| been divided by the scale factor. | |
| """ | |
| name = "Kernel" | |
| def __init__( | |
| self, | |
| size: tuple[int, int], | |
| kernel: Sequence[float], | |
| scale: float | None = None, | |
| offset: float = 0, | |
| ) -> None: | |
| if scale is None: | |
| # default scale is sum of kernel | |
| scale = functools.reduce(lambda a, b: a + b, kernel) | |
| if size[0] * size[1] != len(kernel): | |
| msg = "not enough coefficients in kernel" | |
| raise ValueError(msg) | |
| self.filterargs = size, scale, offset, kernel | |
| class RankFilter(Filter): | |
| """ | |
| Create a rank filter. The rank filter sorts all pixels in | |
| a window of the given size, and returns the ``rank``'th value. | |
| :param size: The kernel size, in pixels. | |
| :param rank: What pixel value to pick. Use 0 for a min filter, | |
| ``size * size / 2`` for a median filter, ``size * size - 1`` | |
| for a max filter, etc. | |
| """ | |
| name = "Rank" | |
| def __init__(self, size: int, rank: int) -> None: | |
| self.size = size | |
| self.rank = rank | |
| def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: | |
| if image.mode == "P": | |
| msg = "cannot filter palette images" | |
| raise ValueError(msg) | |
| image = image.expand(self.size // 2, self.size // 2) | |
| return image.rankfilter(self.size, self.rank) | |
| class MedianFilter(RankFilter): | |
| """ | |
| Create a median filter. Picks the median pixel value in a window with the | |
| given size. | |
| :param size: The kernel size, in pixels. | |
| """ | |
| name = "Median" | |
| def __init__(self, size: int = 3) -> None: | |
| self.size = size | |
| self.rank = size * size // 2 | |
| class MinFilter(RankFilter): | |
| """ | |
| Create a min filter. Picks the lowest pixel value in a window with the | |
| given size. | |
| :param size: The kernel size, in pixels. | |
| """ | |
| name = "Min" | |
| def __init__(self, size: int = 3) -> None: | |
| self.size = size | |
| self.rank = 0 | |
| class MaxFilter(RankFilter): | |
| """ | |
| Create a max filter. Picks the largest pixel value in a window with the | |
| given size. | |
| :param size: The kernel size, in pixels. | |
| """ | |
| name = "Max" | |
| def __init__(self, size: int = 3) -> None: | |
| self.size = size | |
| self.rank = size * size - 1 | |
| class ModeFilter(Filter): | |
| """ | |
| Create a mode filter. Picks the most frequent pixel value in a box with the | |
| given size. Pixel values that occur only once or twice are ignored; if no | |
| pixel value occurs more than twice, the original pixel value is preserved. | |
| :param size: The kernel size, in pixels. | |
| """ | |
| name = "Mode" | |
| def __init__(self, size: int = 3) -> None: | |
| self.size = size | |
| def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: | |
| return image.modefilter(self.size) | |
| class GaussianBlur(MultibandFilter): | |
| """Blurs the image with a sequence of extended box filters, which | |
| approximates a Gaussian kernel. For details on accuracy see | |
| <https://www.mia.uni-saarland.de/Publications/gwosdek-ssvm11.pdf> | |
| :param radius: Standard deviation of the Gaussian kernel. Either a sequence of two | |
| numbers for x and y, or a single number for both. | |
| """ | |
| name = "GaussianBlur" | |
| def __init__(self, radius: float | Sequence[float] = 2) -> None: | |
| self.radius = radius | |
| def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: | |
| xy = self.radius | |
| if isinstance(xy, (int, float)): | |
| xy = (xy, xy) | |
| if xy == (0, 0): | |
| return image.copy() | |
| return image.gaussian_blur(xy) | |
| class BoxBlur(MultibandFilter): | |
| """Blurs the image by setting each pixel to the average value of the pixels | |
| in a square box extending radius pixels in each direction. | |
| Supports float radius of arbitrary size. Uses an optimized implementation | |
| which runs in linear time relative to the size of the image | |
| for any radius value. | |
| :param radius: Size of the box in a direction. Either a sequence of two numbers for | |
| x and y, or a single number for both. | |
| Radius 0 does not blur, returns an identical image. | |
| Radius 1 takes 1 pixel in each direction, i.e. 9 pixels in total. | |
| """ | |
| name = "BoxBlur" | |
| def __init__(self, radius: float | Sequence[float]) -> None: | |
| xy = radius if isinstance(radius, (tuple, list)) else (radius, radius) | |
| if xy[0] < 0 or xy[1] < 0: | |
| msg = "radius must be >= 0" | |
| raise ValueError(msg) | |
| self.radius = radius | |
| def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: | |
| xy = self.radius | |
| if isinstance(xy, (int, float)): | |
| xy = (xy, xy) | |
| if xy == (0, 0): | |
| return image.copy() | |
| return image.box_blur(xy) | |
| class UnsharpMask(MultibandFilter): | |
| """Unsharp mask filter. | |
| See Wikipedia's entry on `digital unsharp masking`_ for an explanation of | |
| the parameters. | |
| :param radius: Blur Radius | |
| :param percent: Unsharp strength, in percent | |
| :param threshold: Threshold controls the minimum brightness change that | |
| will be sharpened | |
| .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking | |
| """ | |
| name = "UnsharpMask" | |
| def __init__( | |
| self, radius: float = 2, percent: int = 150, threshold: int = 3 | |
| ) -> None: | |
| self.radius = radius | |
| self.percent = percent | |
| self.threshold = threshold | |
| def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: | |
| return image.unsharp_mask(self.radius, self.percent, self.threshold) | |
| class BLUR(BuiltinFilter): | |
| name = "Blur" | |
| # fmt: off | |
| filterargs = (5, 5), 16, 0, ( | |
| 1, 1, 1, 1, 1, | |
| 1, 0, 0, 0, 1, | |
| 1, 0, 0, 0, 1, | |
| 1, 0, 0, 0, 1, | |
| 1, 1, 1, 1, 1, | |
| ) | |
| # fmt: on | |
| class CONTOUR(BuiltinFilter): | |
| name = "Contour" | |
| # fmt: off | |
| filterargs = (3, 3), 1, 255, ( | |
| -1, -1, -1, | |
| -1, 8, -1, | |
| -1, -1, -1, | |
| ) | |
| # fmt: on | |
| class DETAIL(BuiltinFilter): | |
| name = "Detail" | |
| # fmt: off | |
| filterargs = (3, 3), 6, 0, ( | |
| 0, -1, 0, | |
| -1, 10, -1, | |
| 0, -1, 0, | |
| ) | |
| # fmt: on | |
| class EDGE_ENHANCE(BuiltinFilter): | |
| name = "Edge-enhance" | |
| # fmt: off | |
| filterargs = (3, 3), 2, 0, ( | |
| -1, -1, -1, | |
| -1, 10, -1, | |
| -1, -1, -1, | |
| ) | |
| # fmt: on | |
| class EDGE_ENHANCE_MORE(BuiltinFilter): | |
| name = "Edge-enhance More" | |
| # fmt: off | |
| filterargs = (3, 3), 1, 0, ( | |
| -1, -1, -1, | |
| -1, 9, -1, | |
| -1, -1, -1, | |
| ) | |
| # fmt: on | |
| class EMBOSS(BuiltinFilter): | |
| name = "Emboss" | |
| # fmt: off | |
| filterargs = (3, 3), 1, 128, ( | |
| -1, 0, 0, | |
| 0, 1, 0, | |
| 0, 0, 0, | |
| ) | |
| # fmt: on | |
| class FIND_EDGES(BuiltinFilter): | |
| name = "Find Edges" | |
| # fmt: off | |
| filterargs = (3, 3), 1, 0, ( | |
| -1, -1, -1, | |
| -1, 8, -1, | |
| -1, -1, -1, | |
| ) | |
| # fmt: on | |
| class SHARPEN(BuiltinFilter): | |
| name = "Sharpen" | |
| # fmt: off | |
| filterargs = (3, 3), 16, 0, ( | |
| -2, -2, -2, | |
| -2, 32, -2, | |
| -2, -2, -2, | |
| ) | |
| # fmt: on | |
| class SMOOTH(BuiltinFilter): | |
| name = "Smooth" | |
| # fmt: off | |
| filterargs = (3, 3), 13, 0, ( | |
| 1, 1, 1, | |
| 1, 5, 1, | |
| 1, 1, 1, | |
| ) | |
| # fmt: on | |
| class SMOOTH_MORE(BuiltinFilter): | |
| name = "Smooth More" | |
| # fmt: off | |
| filterargs = (5, 5), 100, 0, ( | |
| 1, 1, 1, 1, 1, | |
| 1, 5, 5, 5, 1, | |
| 1, 5, 44, 5, 1, | |
| 1, 5, 5, 5, 1, | |
| 1, 1, 1, 1, 1, | |
| ) | |
| # fmt: on | |
| class Color3DLUT(MultibandFilter): | |
| """Three-dimensional color lookup table. | |
| Transforms 3-channel pixels using the values of the channels as coordinates | |
| in the 3D lookup table and interpolating the nearest elements. | |
| This method allows you to apply almost any color transformation | |
| in constant time by using pre-calculated decimated tables. | |
| .. versionadded:: 5.2.0 | |
| :param size: Size of the table. One int or tuple of (int, int, int). | |
| Minimal size in any dimension is 2, maximum is 65. | |
| :param table: Flat lookup table. A list of ``channels * size**3`` | |
| float elements or a list of ``size**3`` channels-sized | |
| tuples with floats. Channels are changed first, | |
| then first dimension, then second, then third. | |
| Value 0.0 corresponds lowest value of output, 1.0 highest. | |
| :param channels: Number of channels in the table. Could be 3 or 4. | |
| Default is 3. | |
| :param target_mode: A mode for the result image. Should have not less | |
| than ``channels`` channels. Default is ``None``, | |
| which means that mode wouldn't be changed. | |
| """ | |
| name = "Color 3D LUT" | |
| def __init__( | |
| self, | |
| size: int | tuple[int, int, int], | |
| table: Sequence[float] | Sequence[Sequence[int]] | NumpyArray, | |
| channels: int = 3, | |
| target_mode: str | None = None, | |
| **kwargs: bool, | |
| ) -> None: | |
| if channels not in (3, 4): | |
| msg = "Only 3 or 4 output channels are supported" | |
| raise ValueError(msg) | |
| self.size = size = self._check_size(size) | |
| self.channels = channels | |
| self.mode = target_mode | |
| # Hidden flag `_copy_table=False` could be used to avoid extra copying | |
| # of the table if the table is specially made for the constructor. | |
| copy_table = kwargs.get("_copy_table", True) | |
| items = size[0] * size[1] * size[2] | |
| wrong_size = False | |
| numpy: ModuleType | None = None | |
| if hasattr(table, "shape"): | |
| try: | |
| import numpy | |
| except ImportError: | |
| pass | |
| if numpy and isinstance(table, numpy.ndarray): | |
| numpy_table: NumpyArray = table | |
| if copy_table: | |
| numpy_table = numpy_table.copy() | |
| if numpy_table.shape in [ | |
| (items * channels,), | |
| (items, channels), | |
| (size[2], size[1], size[0], channels), | |
| ]: | |
| table = numpy_table.reshape(items * channels) | |
| else: | |
| wrong_size = True | |
| else: | |
| if copy_table: | |
| table = list(table) | |
| # Convert to a flat list | |
| if table and isinstance(table[0], (list, tuple)): | |
| raw_table = cast(Sequence[Sequence[int]], table) | |
| flat_table: list[int] = [] | |
| for pixel in raw_table: | |
| if len(pixel) != channels: | |
| msg = ( | |
| "The elements of the table should " | |
| f"have a length of {channels}." | |
| ) | |
| raise ValueError(msg) | |
| flat_table.extend(pixel) | |
| table = flat_table | |
| if wrong_size or len(table) != items * channels: | |
| msg = ( | |
| "The table should have either channels * size**3 float items " | |
| "or size**3 items of channels-sized tuples with floats. " | |
| f"Table should be: {channels}x{size[0]}x{size[1]}x{size[2]}. " | |
| f"Actual length: {len(table)}" | |
| ) | |
| raise ValueError(msg) | |
| self.table = table | |
| def _check_size(size: Any) -> tuple[int, int, int]: | |
| try: | |
| _, _, _ = size | |
| except ValueError as e: | |
| msg = "Size should be either an integer or a tuple of three integers." | |
| raise ValueError(msg) from e | |
| except TypeError: | |
| size = (size, size, size) | |
| size = tuple(int(x) for x in size) | |
| for size_1d in size: | |
| if not 2 <= size_1d <= 65: | |
| msg = "Size should be in [2, 65] range." | |
| raise ValueError(msg) | |
| return size | |
| def generate( | |
| cls, | |
| size: int | tuple[int, int, int], | |
| callback: Callable[[float, float, float], tuple[float, ...]], | |
| channels: int = 3, | |
| target_mode: str | None = None, | |
| ) -> Color3DLUT: | |
| """Generates new LUT using provided callback. | |
| :param size: Size of the table. Passed to the constructor. | |
| :param callback: Function with three parameters which correspond | |
| three color channels. Will be called ``size**3`` | |
| times with values from 0.0 to 1.0 and should return | |
| a tuple with ``channels`` elements. | |
| :param channels: The number of channels which should return callback. | |
| :param target_mode: Passed to the constructor of the resulting | |
| lookup table. | |
| """ | |
| size_1d, size_2d, size_3d = cls._check_size(size) | |
| if channels not in (3, 4): | |
| msg = "Only 3 or 4 output channels are supported" | |
| raise ValueError(msg) | |
| table: list[float] = [0] * (size_1d * size_2d * size_3d * channels) | |
| idx_out = 0 | |
| for b in range(size_3d): | |
| for g in range(size_2d): | |
| for r in range(size_1d): | |
| table[idx_out : idx_out + channels] = callback( | |
| r / (size_1d - 1), g / (size_2d - 1), b / (size_3d - 1) | |
| ) | |
| idx_out += channels | |
| return cls( | |
| (size_1d, size_2d, size_3d), | |
| table, | |
| channels=channels, | |
| target_mode=target_mode, | |
| _copy_table=False, | |
| ) | |
| def transform( | |
| self, | |
| callback: Callable[..., tuple[float, ...]], | |
| with_normals: bool = False, | |
| channels: int | None = None, | |
| target_mode: str | None = None, | |
| ) -> Color3DLUT: | |
| """Transforms the table values using provided callback and returns | |
| a new LUT with altered values. | |
| :param callback: A function which takes old lookup table values | |
| and returns a new set of values. The number | |
| of arguments which function should take is | |
| ``self.channels`` or ``3 + self.channels`` | |
| if ``with_normals`` flag is set. | |
| Should return a tuple of ``self.channels`` or | |
| ``channels`` elements if it is set. | |
| :param with_normals: If true, ``callback`` will be called with | |
| coordinates in the color cube as the first | |
| three arguments. Otherwise, ``callback`` | |
| will be called only with actual color values. | |
| :param channels: The number of channels in the resulting lookup table. | |
| :param target_mode: Passed to the constructor of the resulting | |
| lookup table. | |
| """ | |
| if channels not in (None, 3, 4): | |
| msg = "Only 3 or 4 output channels are supported" | |
| raise ValueError(msg) | |
| ch_in = self.channels | |
| ch_out = channels or ch_in | |
| size_1d, size_2d, size_3d = self.size | |
| table: list[float] = [0] * (size_1d * size_2d * size_3d * ch_out) | |
| idx_in = 0 | |
| idx_out = 0 | |
| for b in range(size_3d): | |
| for g in range(size_2d): | |
| for r in range(size_1d): | |
| values = self.table[idx_in : idx_in + ch_in] | |
| if with_normals: | |
| values = callback( | |
| r / (size_1d - 1), | |
| g / (size_2d - 1), | |
| b / (size_3d - 1), | |
| *values, | |
| ) | |
| else: | |
| values = callback(*values) | |
| table[idx_out : idx_out + ch_out] = values | |
| idx_in += ch_in | |
| idx_out += ch_out | |
| return type(self)( | |
| self.size, | |
| table, | |
| channels=ch_out, | |
| target_mode=target_mode or self.mode, | |
| _copy_table=False, | |
| ) | |
| def __repr__(self) -> str: | |
| r = [ | |
| f"{self.__class__.__name__} from {self.table.__class__.__name__}", | |
| "size={:d}x{:d}x{:d}".format(*self.size), | |
| f"channels={self.channels:d}", | |
| ] | |
| if self.mode: | |
| r.append(f"target_mode={self.mode}") | |
| return "<{}>".format(" ".join(r)) | |
| def filter(self, image: _imaging.ImagingCore) -> _imaging.ImagingCore: | |
| from . import Image | |
| return image.color_lut_3d( | |
| self.mode or image.mode, | |
| Image.Resampling.BILINEAR, | |
| self.channels, | |
| self.size, | |
| self.table, | |
| ) | |