o
    :ήc(]                     @   s  d dl mZ d dlZd dlZd dlZd dlZd dlm	Z	 dd Z
dd Zdd	 Zd
d Zd;ddZdd Zd<ddZd=ddZd>ddZdd Zd?ddZdd  Zd@d!d"Zd#d$ Zd%d& Zd'd( Zd)d* ZdAd,d-ZdBd/d0Zd1d2 Zd3d4 ZdCd5d6Zd7d8 Z d9d: Z!dS )D    )ImageNwrapsc                    s   t   fdd}|S )a  Creates a new function which operates on each channel

    Parameters
    ----------
    single_channel_func: function
        Function that acts on a single color channel

    Returns
    -------
    channel_func: function
        The same function that operates on all color channels

    Example
    -------
    >>> from pymatting import *
    >>> import numpy as np
    >>> from scipy.signal import convolve2d
    >>> single_channel_fun = lambda x: convolve2d(x, np.ones((3, 3)), 'valid')
    >>> multi_channel_fun = apply_to_channels(single_channel_fun)
    >>> I = np.random.rand(480, 320, 3)
    >>> multi_channel_fun(I).shape
    (478, 318, 3)
    c                    s   t jdkrg R i S j}|d |d dtj fddtjd D dd}|t|jd d t|dd   S )N   r      c                    s8   g | ]}d d d d |f   g R i qS N)copy).0c)argsimagekwargssingle_channel_func :/tmp/pip-target-vg8gfxp4/lib/python/pymatting/util/util.py
<listcomp>+   s    *zAapply_to_channels.<locals>.multi_channel_func.<locals>.<listcomp>axis)lenshapereshapenpstackrangelist)r   r   r   r   resultr   )r   r   r   r   multi_channel_func"   s   (z-apply_to_channels.<locals>.multi_channel_funcr   )r   r   r   r   r   apply_to_channels	   s   r   c                 C      t d| |S )a0  Computes the dot product of two vectors.

    Parameters
    ----------
    a: numpy.ndarray
        First vector (if np.ndim(a) > 1 the function calculates the product for the two last axes)
    b: numpy.ndarray
        Second vector (if np.ndim(b) > 1 the function calculates the product for the two last axes)

    Returns
    -------
    product: scalar
        Dot product of `a` and `b`

    Example
    -------
    >>> import numpy as np
    >>> from pymatting import *
    >>> a = np.ones(2)
    >>> b = np.ones(2)
    >>> vec_vec_dot(a,b)
    2.0
    z...i,...i->...r   einsumabr   r   r   vec_vec_dot7      r&   c                 C   r    )aK  Calculates the matrix vector product for two arrays.

    Parameters
    ----------
    A: numpy.ndarray
        Matrix (if np.ndim(A) > 2 the function calculates the product for the two last axes)
    b: numpy.ndarray
        Vector (if np.ndim(b) > 1 the function calculates the product for the two last axes)

    Returns
    -------
    product: numpy.ndarray
        Matrix vector product of both arrays

    Example
    -------
    >>> import numpy as np
    >>> from pymatting import *
    >>> A = np.eye(2)
    >>> b = np.ones(2)
    >>> mat_vec_dot(A,b)
    array([1., 1.])
    z...ij,...j->...ir!   )Ar%   r   r   r   mat_vec_dotR   r'   r)   c                 C   r    )aV  Computes the outer product of two vectors

    a: numpy.ndarray
        First vector (if np.ndim(b) > 1 the function calculates the product for the two last axes)
    b: numpy.ndarray
        Second vector (if np.ndim(b) > 1 the function calculates the product for the two last axes)

    Returns
    -------
    product: numpy.ndarray
        Outer product of `a` and `b` as numpy.ndarray

    Example
    -------
    >>> import numpy as np
    >>> from pymatting import *
    >>> a = np.arange(1,3)
    >>> b = np.arange(1,3)
    >>> vec_vec_outer(a,b)
    array([[1, 2],
           [2, 4]])
    z	...i,...jr!   r#   r   r   r   vec_vec_outerm   s   r*   皙??c                 C   sr   |dk s|dkrt d|dk s|dkrt d||kr t d| |k }| |k}dt|  }d||< d||< |S )a  Fixes broken trimap :math:`T` by thresholding the values

    .. math::
        T^{\text{fixed}}_{ij}=
        \begin{cases}
            0,&\text{if } T_{ij}<\text{lower_threshold}\\
            1,&\text{if }T_{ij}>\text{upper_threshold}\\
            0.5, &\text{otherwise}.\\
        \end{cases}


    Parameters
    ----------
    trimap: numpy.ndarray
        Possibly broken trimap

    lower_threshold: float
        Threshold used to determine background pixels, defaults to 0.1

    upper_threshold: float
        Threshold used to determine foreground pixels, defaults to 0.9

    Returns
    -------
    fixed_trimap: numpy.ndarray
        Trimap having values in :math:`\{0, 0.5, 1\}`

    Example
    -------
    >>> from pymatting import *
    >>> import numpy as np
    >>> trimap = np.array([0,0.1, 0.4, 0.9, 1])
    >>> fix_trimap(trimap, 0.2, 0.8)
    array([0. , 0. , 0.5, 1. , 1. ])
    r   r   zInvalid lower thresholdzInvalid upper thresholdz4Lower threshold must be smaller than upper thresholdg      ?)
ValueErrorr   	ones_like)trimaplower_thresholdupper_thresholdis_bgis_fgfixedr   r   r   
fix_trimap   s   $r5   c                 C   s$   zt |  W dS  ty   Y dS w )aC  Checks if an object is iterable

    Parameters
    ----------
    obj: object
        Object to check

    Returns
    -------
    is_iterable: bool
        Boolean variable indicating wether the object is iterable

    Example
    -------
    >>> from pymatting import *
    >>> l = []
    >>> isiterable(l)
    True
    TF)iter	TypeError)objr   r   r   
isiterable   s   r9   bicubicc                 C   s^   t jt jt jt jt jt jt jd}t|s#t| j	| t| j
| f}| |||  } | S )N)r:   bilinearboxhamminglanczosnearestnone)r   BICUBICBILINEARBOXHAMMINGLANCZOSNEARESTr9   intwidthheightresizelower)r   sizeresamplefiltersr   r   r   _resize_pil_image   s   
rO   r<   c                 C   sZ   t | }|dur| }|dkrdn|}||}|dur$t|||}t|d }|S )a   This function can be used to load an image from a file.

    Parameters
    ----------
    path: str
        Path of image to load.
    mode: str
        Can be "GRAY", "RGB" or something else (see PIL.convert())

    Returns
    -------
    image: numpy.ndarray
        Loaded image
    NGRAYLg     o@)r   openupperconvertrO   r   array)pathmoderL   rM   r   r   r   r   
load_image   s   

rX   Tc                 C   s   |j tjtjtjfv sJ |j tjtjfv r#t|d ddtj}|r:tj	| \}}t
|dkr:tj|dd t||  dS )a  Given a path, save an image there.

    Parameters
    ----------
    path: str
        Where to save the image.
    image: numpy.ndarray, dtype in [np.uint8, np.float32, np.float64]
        Image to save.
        Images of float dtypes should be in range [0, 1].
        Images of uint8 dtype should be in range [0, 255]
    make_directory: bool
        Whether to create the directories needed for the image path.
       r   T)exist_okN)dtyper   uint8float32float64clipastypeosrV   splitr   makedirsr   	fromarraysave)rV   r   make_directory	directory_r   r   r   
save_image	  s   ri   c                 C   s   t | jdv s	J | jtjtjtjfv sJ | jtjtjfv r,t| d ddtj} t | jdkr=tj	| gd ddS | jd dkrNtj
| gd ddS | jd dkrW| S | jd dkrk| d	d	d	d	d	df S td
| j)a  Convertes an image to rgb8 color space

    Parameters
    ----------
    image: numpy.ndarray
        Image to convert

    Returns
    -------
    image: numpy.ndarray
        Converted image with same height and width as input image but with three color channels
    Example
    -------
    >>> from pymatting import *
    >>> import numpy as np
    >>> I = np.eye(2)
    >>> to_rgb8(I)
    array([[[255, 255, 255],
            [  0,   0,   0]],
           [[  0,   0,   0],
            [255, 255, 255]]], dtype=uint8)
    )r      rY   r   r   rj   r   r      NzInvalid image shape:)r   r   r[   r   r\   r]   r^   r_   r`   r   concatenater-   )r   r   r   r   to_rgb8$  s   rm   c              	   C   s  | D ]}|dur|j tjtjfv sJ qt| }|dkrdS |du r9|du r9ttt|}|| d | }n|du rF|| d | }n|du rR|| d | }dd | D }tdd |D }tdd |D }td	d |D dd
}	|	dkrt	| D ]K\}
}|durt|j
dkr|ddddtjf }|j
d dkrtj|g|	 dd}|j
d dkr|	dkrt|tj|j
dd |j d}|| |
< q~|du rtdd | D }tj|| || |	f|d}t|D ]I}t|D ]B}|||  }
|
t| kr n3| |
 }|dur1||j
d |j
d d}|||| || |j
d  || || |j
d  f< qq|j
d dkrF|dddddf }|S )aU  Plots a grid of images.

    Parameters
    ----------
    images : list of numpy.ndarray
        List of images to plot
    nx: int
        Number of rows
    ny: int
        Number of columns
    dtype: type
        Data type of output array

    Returns
    -------
    grid: numpy.ndarray
       Grid of images with datatype `dtype`
    Nr   r   c                 S   s   g | ]	}|d ur|j qS r   r   r
   r   r   r   r   r   v  s    zmake_grid.<locals>.<listcomp>c                 s       | ]}|d  V  qdS )r   Nr   r
   r   r   r   r   	<genexpr>x      zmake_grid.<locals>.<genexpr>c                 s   rp   )r   Nr   rq   r   r   r   rr   y  rs   c                 S   s    g | ]}t |d kr|d  qS )r   )r   rq   r   r   r   r   z  s     )defaultr   r   rj   rk   r[   c                 s   s    | ]
}|d ur|j V  qd S r   ru   ro   r   r   r   rr     s    r   )r[   r   r]   r^   r   rG   ceilsqrtmax	enumerater   newaxisrl   stack_imagesonesnextzerosr   r   )imagesnxnyr[   r   nshapeshwdir   yxr   r   r   	make_gridP  sd   
2r   c                 C   s8   t | }t|d ddtj}t|}|  dS )zPlot grid of images.

    Parameters
    ----------
    images : list of numpy.ndarray
        List of images to plot
    height : int, matrix
        Height in pixels the output grid, defaults to 512

    rY   r   N)r   r   r_   r`   r\   r   rd   show)r   gridr   r   r   show_images  s   
r   c           
      C   s   |r|   } |  }|  }|dk rtjd| dd |dkr(tjd| dd | jtjtjfvr;tjd| j dd | |k}| |k}|	 dkrOt
d	| |	 dkr[t
d
| ||B }| }	||||	fS )a  This function splits the trimap into foreground pixels, background pixels, and unknown pixels.

    Foreground pixels are pixels where the trimap has values larger than or equal to `fg_threshold` (default: 0.9). 
    Background pixels are pixels where the trimap has values smaller than or equal to `bg_threshold` (default: 0.1).
    Pixels with other values are assumed to be unknown.

    Parameters
    ----------
    trimap: numpy.ndarray
        Trimap with shape :math:`h \times w`
    flatten: bool
        If true np.flatten is called on the trimap

    Returns
    -------
    is_fg: numpy.ndarray
        Boolean array indicating which pixel belongs to the foreground
    is_bg: numpy.ndarray
        Boolean array indicating which pixel belongs to the background
    is_known: numpy.ndarray
        Boolean array indicating which pixel is known
    is_unknown: numpy.ndarray
        Boolean array indicating which pixel is unknown
    bg_threshold: float
        Pixels with smaller trimap values will be considered background.
    fg_threshold: float
        Pixels with larger trimap values will be considered foreground.


    Example
    -------
    >>> import numpy as np
    >>> from pymatting import *
    >>> trimap = np.array([[1,0],[0.5,0.2]])
    >>> is_fg, is_bg, is_known, is_unknown = trimap_split(trimap)
    >>> is_fg
    array([ True, False, False, False])
    >>> is_bg
    array([False,  True, False, False])
    >>> is_known
    array([ True,  True, False, False])
    >>> is_unknown
    array([False, False,  True,  True])
            z:Trimap values should be in [0, 1], but trimap.min() is %s.rj   
stacklevel      ?z:Trimap values should be in [0, 1], but trimap.max() is %s.zfUnexpected trimap.dtype %s. Are you sure that you do not want to use np.float32 or np.float64 instead?r   z7Trimap did not contain background values (values <= %f)z7Trimap did not contain foreground values (values >= %f))flattenminrx   warningswarnr[   r   r]   r^   sumr-   )
r/   r   bg_thresholdfg_threshold	min_value	max_valuer3   r2   is_known
is_unknownr   r   r   trimap_split  sD   -r   c                 C   s   t | jdks| jd dkrtjdt| j dd |  }|  }|dk r/tjd| dd |dkr<tjd| dd | jtj	tj
fvrQtjd	| j dd d
S d
S )a  Performs a sanity check for input images. Image values should be in the
    range [0, 1], the `dtype` should be `np.float32` or `np.float64` and the
    image shape should be `(?, ?, 3)`.

    Parameters
    ----------
    image: numpy.ndarray
        Image with shape :math:`h \times w \times 3`

    Example
    -------
    >>> import numpy as np
    >>> from pymatting import check_image
    >>> image = (np.random.randn(64, 64, 2) * 255).astype(np.int32)
    >>> sanity_check_image(image)
    __main__:1: UserWarning: Expected RGB image of shape (?, ?, 3), but image.shape is (64, 64, 2).
    __main__:1: UserWarning: Image values should be in [0, 1], but image.min() is -933.
    __main__:1: UserWarning: Image values should be in [0, 1], but image.max() is 999.
    __main__:1: UserWarning: Unexpected image.dtype int32. Are you sure that you do not want to use np.float32 or np.float64 instead?

    rj   r   z=Expected RGB image of shape (?, ?, 3), but image.shape is %s.r   r   z8Image values should be in [0, 1], but image.min() is %s.r   z8Image values should be in [0, 1], but image.max() is %s.zeUnexpected image.dtype %s. Are you sure that you do not want to use np.float32 or np.float64 instead?N)r   r   r   r   strr   rx   r[   r   r]   r^   )r   r   r   r   r   r   sanity_check_image  s6   
r   c                 C   s:   t |jdkr|ddddtjf }||  d| |  S )a  This function composes a new image for given foreground image, background image and alpha matte.

    This is done by applying the composition equation

    .. math::
        I = \alpha F + (1-\alpha)B.

    Parameters
    ----------
    foreground: numpy.ndarray
        Foreground image
    background: numpy.ndarray
        Background image
    alpha: numpy.ndarray
        Alpha matte

    Returns
    -------
    image: numpy.ndarray
        Composed image as numpy.ndarray

    Example
    -------
    >>> from pymatting import *
    >>> foreground = load_image("data/lemur/lemur_foreground.png", "RGB")
    >>> background = load_image("data/lemur/beach.png", "RGB")
    >>> alpha = load_image("data/lemur/lemur_alpha.png", "GRAY")
    >>> I = blend(foreground, background, alpha)
    r   Nr   r   r   r   rz   )
foreground
backgroundalphar   r   r   blendG  s   r   c                  G   s   dd | D } t j| ddS )a  This function stacks images along the third axis.
    This is useful for combining e.g. rgb color channels or color and alpha channels.

    Parameters
    ----------
    *images: numpy.ndarray
        Images to be stacked.

    Returns
    -------
    image: numpy.ndarray
        Stacked images as numpy.ndarray

    Example
    -------
    >>> from pymatting.util.util import stack_images
    >>> import numpy as np
    >>> I = stack_images(np.random.rand(4,5,3), np.random.rand(4,5,3))
    >>> I.shape
    (4, 5, 6)
    c                 S   s6   g | ]}t |jd kr|n|ddddtjf qS )rj   Nr   ro   r   r   r   r     s    (z stack_images.<locals>.<listcomp>r   r   )r   rl   )r   r   r   r   r{   k  s   r{   c                 C   s   |  t| jd | j}|S )a  Calculate the sum of each row of a matrix

    Parameters
    ----------
    A: np.ndarray or scipy.sparse.spmatrix
        Matrix to sum rows of

    Returns
    -------
    row_sums: np.ndarray
        Vector of summed rows

    Example
    -------
    >>> from pymatting import *
    >>> import numpy as np
    >>> A = np.random.rand(2,2)
    >>> A
    array([[0.62750946, 0.12917617],
           [0.8599449 , 0.5777254 ]])
    >>> row_sum(A)
    array([0.75668563, 1.4376703 ])
    r   )dotr   r|   r   r[   )r(   row_sumsr   r   r   row_sum  s   r   r   c                 C   s6   t | }d|||k < d| }tj|}|| } | S )a  Normalize the rows of a matrix

    Rows with sum below threshold are left as-is.

    Parameters
    ----------
    A: scipy.sparse.spmatrix
        Matrix to normalize
    threshold: float
        Threshold to avoid division by zero

    Returns
    -------
    A: scipy.sparse.spmatrix
        Matrix with normalized rows

    Example
    -------
    >>> from pymatting import *
    >>> import numpy as np
    >>> A = np.arange(4).reshape(2,2)
    >>> normalize_rows(A)
    array([[0. , 1. ],
           [0.4, 0.6]])
    r   )r   scipysparsediagsr   )r(   	thresholdr   row_normalization_factorsDr   r   r   normalize_rows  s   
r   Fc                 C   s\   |rt t | |}t t || }||fS t | }t |}t ||\}}||fS )ap  Calculates image pixel coordinates for an image with a specified shape

    Parameters
    ----------
    width: int
        Width of the input image
    height: int
        Height of the input image
    flatten: bool
        Whether the array containing the coordinates should be flattened or not, defaults to False

    Returns
    -------
    x: numpy.ndarray
        x coordinates
    y: numpy.ndarray
        y coordinates

    Example
    -------
    >>> from pymatting import *
    >>> x, y = grid_coordinates(2,2)
    >>> x
    array([[0, 1],
           [0, 1]])
    >>> y
    array([[0, 0],
           [1, 1]])
    )r   tilearangerepeatmeshgrid)rH   rI   r   r   r   r   r   r   grid_coordinates  s   

r   c                 C   s  t | }t|}| | }t j|| t jd}t j|| t jd}	t j|| t jd}
d}t| |dd\}}t|||D ]?\}}}t 	|| d| d }t 	|| d|d }|||   |||| < |||   |	||| < ||
||| < ||7 }q>t
jj|
||	ff||fd}|S )a  Calculates a convolution matrix that can be applied to a vectorized image

    Additionaly, this function allows to specify which pixels should be used for the convoltion, i.e.

    .. math:: \left(I * K\right)_{ij} = \sum_k K_k I_{i+{\Delta_y}_k,j+{\Delta_y}_k},

    where :math:`K` is the flattened convolution kernel.

    Parameters
    ----------
    width: int
        Width of the input image
    height: int
        Height of the input image
    kernel: numpy.ndarray
        Convolutional kernel
    dx: numpy.ndarray
        Offset in x direction
    dy: nunpy.ndarray
        Offset in y direction

    Returns
    -------
    M: scipy.sparse.csr_matrix
        Convolution matrix
    ru   r   Tr   r   rn   )r   asarrayr   r   r~   int32r^   r   zipr_   r   r   
csr_matrix)rH   rI   kerneldxdyweightscountr   i_indsj_indsvalueskr   r   dx2dy2weightx2y2r(   r   r   r   sparse_conv_matrix_with_offsets  s"   
r   c                 C   sD   |j \}}t||dd\}}||d 8 }||d 8 }t| ||||S )aR  Calculates a convolution matrix that can be applied to a vectorized image

    Parameters
    ----------
    width: int
        Width of the input image
    height: int
        Height of the input image
    kernel: numpy.ndarray
        Convolutional kernel

    Returns
    -------
    M: scipy.sparse.csr_matrix
        Convolution matrix

    Example
    -------
    >>> from pymatting import *
    >>> import numpy as np
    >>> sparse_conv_matrix(3,3,np.ones((3,3)))
    <9x9 sparse matrix of type '<class 'numpy.float64'>'
    with 49 stored elements in Compressed Sparse Row format>
    Tr   r   )r   r   r   )rH   rI   r   khkwr   r   r   r   r   sparse_conv_matrix)  s
   
r   c                 C   s0   |rt | } |t|  }tj|}||  }|S )a  Calculates the random walk normlized Laplacian matrix from the weight matrix

    Parameters
    ----------
    W: numpy.ndarray
        Array of weights
    normalize: bool
        Whether the rows of W should be normalized to 1, defaults to True
    regularization: float
        Regularization strength, defaults to 0, i.e. no regularizaion

    Returns
    -------
    L: scipy.sparse.spmatrix
        Laplacian matrix

    Example
    -------
    >>> from pymatting import *
    >>> import numpy as np
    >>> weights_to_laplacian(np.ones((4,4)))
    matrix([[ 0.75, -0.25, -0.25, -0.25],
            [-0.25,  0.75, -0.25, -0.25],
            [-0.25, -0.25,  0.75, -0.25],
            [-0.25, -0.25, -0.25,  0.75]])
    )r   r   r   r   r   )W	normalizeregularizationr   r   rQ   r   r   r   weights_to_laplacianJ  s   r   c                 C   s*   t | } |  }|  }| | ||  S )a  Normalizes an array such that all values are between 0 and 1

    Parameters
    ----------
    values: numpy.ndarray
        Array to normalize

    Returns
    -------
    result: numpy.ndarray
        Normalized array

    Example
    -------
    >>> from pymatting import *
    >>> import numpy as np
    >>> normalize(np.array([0, 1, 3, 10]))
    array([0. , 0.1, 0.3, 1. ])
    )r   r   r   rx   )r   r$   r%   r   r   r   r   p  s   
r   c                 C   s   | | d | S )a1  Divides a number x by another integer n and rounds up the result

    Parameters
    ----------
    x: int
        Numerator
    n: int
        Denominator

    Returns
    -------
    result: int
        Result

    Example
    -------
    >>> from pymatting import *
    >>> div_round_up(3,2)
    2
    r   r   )r   r   r   r   r   div_round_up  s   r   )r+   r,   )r:   )NNr<   )T)NNN)Tr+   r,   )r   )F)Tr   )"PILr   numpyr   scipy.sparser   ra   r   	functoolsr   r   r&   r)   r*   r5   r9   rO   rX   ri   rm   r   r   r   r   r   r{   r   r   r   r   r   r   r   r   r   r   r   r   <module>   s<    .
5



,W
Y5$

(*2
!&