import numpy as np

"""
Explanation: Table of Contents
<p><div class="lev1 toc-item"><a href="#Many-ways-to-create-a-chess-like-image-in-Python/Numpy" data-toc-modified-id="Many-ways-to-create-a-chess-like-image-in-Python/Numpy-1"><span class="toc-item-num">1&nbsp;&nbsp;</span>Many ways to create a chess-like image in Python/Numpy</a></div><div class="lev2 toc-item"><a href="#Programming-styles" data-toc-modified-id="Programming-styles-11"><span class="toc-item-num">1.1&nbsp;&nbsp;</span>Programming styles</a></div><div class="lev3 toc-item"><a href="#C-style-programming" data-toc-modified-id="C-style-programming-111"><span class="toc-item-num">1.1.1&nbsp;&nbsp;</span>C style programming</a></div><div class="lev3 toc-item"><a href="#Image-coordinates" data-toc-modified-id="Image-coordinates-112"><span class="toc-item-num">1.1.2&nbsp;&nbsp;</span>Image coordinates</a></div><div class="lev3 toc-item"><a href="#Image-coordinates-and-broadcast" data-toc-modified-id="Image-coordinates-and-broadcast-113"><span class="toc-item-num">1.1.3&nbsp;&nbsp;</span>Image coordinates and broadcast</a></div><div class="lev3 toc-item"><a href="#List-implicit-loop-(list-comprehension)" data-toc-modified-id="List-implicit-loop-(list-comprehension)-114"><span class="toc-item-num">1.1.4&nbsp;&nbsp;</span>List implicit loop (list comprehension)</a></div><div class="lev3 toc-item"><a href="#Slice" data-toc-modified-id="Slice-115"><span class="toc-item-num">1.1.5&nbsp;&nbsp;</span>Slice</a></div><div class="lev3 toc-item"><a href="#Line-and-column-replication" data-toc-modified-id="Line-and-column-replication-116"><span class="toc-item-num">1.1.6&nbsp;&nbsp;</span>Line and column replication</a></div><div class="lev3 toc-item"><a href="#Tile" data-toc-modified-id="Tile-117"><span class="toc-item-num">1.1.7&nbsp;&nbsp;</span>Tile</a></div><div class="lev3 toc-item"><a href="#Resize" data-toc-modified-id="Resize-118"><span class="toc-item-num">1.1.8&nbsp;&nbsp;</span>Resize</a></div><div class="lev2 toc-item"><a href="#Comparing-execution-times" data-toc-modified-id="Comparing-execution-times-12"><span class="toc-item-num">1.2&nbsp;&nbsp;</span>Comparing execution times</a></div><div class="lev2 toc-item"><a href="#Conclusions" data-toc-modified-id="Conclusions-13"><span class="toc-item-num">1.3&nbsp;&nbsp;</span>Conclusions</a></div>

# Many ways to create a chess-like image in Python/Numpy

This demonstration illustrates many different ways to create a chess-like image in Python.
Each way has a different concept, but most of them uses the property that each pixel is 0
or 1 depending if the sum of its coordinates is even or odd.
The most efficient way is the technique using slices and the most general one is the one that uses indices.

These examples below is a good introduction to program Python efficiently for image processing applications and verify that processing arrays in Python is very efficient.

The image that we want to generate can be describe in the following equation:

$$   f(row,col) = (row+col) \bmod 2 $$

## Programming styles

We show below seven different programs, some very efficient as uses intrinsic implementation in NumPy, others not so efficient.
End of explanation
"""


def chessCstyle(H,W):
   f = np.empty((H,W),'uint8')
   for row in range(H):
     for col in range(W):
       f[row,col] = (row+col)%2
   return f

print("C Style:\n", chessCstyle(4,8))

"""
Explanation: C style programming
This is the traditional way that a C programmer would solve the problem - scan
every pixel and apply the equation.
End of explanation
"""


def chessIndice(H,W):
    row,col = np.indices((H,W), 'uint16')
    return (row+col)%2

print("Indices:\n", chessIndice(4,8))

"""
Explanation: Image coordinates
This is a general way to create images when we can write the equation of each pixel value
based on its coordinates. The function indices is very handy to create arrays of each coordinate
make possible the array processing of the whole image without the explicit scanning of the pixels in the image.
End of explanation
"""


def chessIndiceBroad(H,W):
    row = np.arange(H).reshape(H,1).astype('uint16')
    col = np.arange(W).reshape(1,W).astype('uint16')
    return (row+col)%2

print("IndiceBroad:\n", chessIndiceBroad(4,8))

"""
Explanation: Image coordinates and broadcast
In the previous indices example, the arrays row and col are two-dimensional arrays. It is possible to
use the broadcast facility of NumPy. We create a row as one column 2D array and col as a one row 2D array
and let the arithmetic operation create the full 2D array using broadcast.
End of explanation
"""


import numpy as np
def chessIter(H,W):
   f = [[ (row+col)%2 for col in range(W) ] for row in range(H) ]
   return np.array(f,'uint8')

print("Iter:\n", chessIter(4,8))

"""
Explanation: List implicit loop (list comprehension)
This solution uses the native implicit loop in list generation.
The list is then converted to array.
End of explanation
"""


def chessSlice(H,W):
    f = np.zeros((H,W), 'uint8')
    f[0::2,1::2] = 1
    f[1::2,0::2] = 1
    return f

print("Slice:\n", chessSlice(4,8))

"""
Explanation: Slice
Slicing is a powerful index access to elements of an array available in numpy.
This solution is one of the most efficient. The image is initially create with zeros and
later filled with ones in the even lines and after in odd lines, always scanning the image
every two pixels.
End of explanation
"""


def chessRep(H,W):
    p = np.array([[0,1],
                  [1,0]],'uint8')
    fCol = p 

    # First column
    for row in range (1, H//2):
        fCol = np.vstack((fCol , p))
    f = fCol
    # Column replication
    for col in range (1, W//2):
        f = np.hstack((f, fCol))
    return f

print("Replication:\n", chessRep(4,8))

"""
Explanation: Line and column replication
This solution has three main parts: in the first a 4 pixel basic pattern is created; in the second
this pattern is replicated line-wise and the last and third part, the replication is done column-wise.
Use the functions vstack and hstack.
End of explanation
"""


def chessTile(H,W):
    p = np.array([[0,1],
                  [1,0]], 'uint8')    
    f = np.tile(p, (H//2, W//2))
    return f

print("Tiling:\n", chessTile(4,8))

"""
Explanation: Tile
This solution uses the properties of the Numpy function tile. It replicates the 4-pixel pattern over
the image according to the number of replication in the vertical and horizontal directions.
End of explanation
"""


def chessResize(H,W):
    # create two lines and then apply resize
    row1 = np.arange(W).astype('uint8') % 2
    row2 = np.array([row1, 1 - row1])
    f = np.resize(row2, (H,W))
    return f

print("Resize:\n", chessResize(4,8))

"""
Explanation: Resize
This solution uses the Numpy function resize to increase the image until the desired size, replicating
the initial raster image contents. We first create two lines of the image and then rezise it until the
final dimension.
End of explanation
"""



H,W = 2000,2000
funlist = (chessResize, chessSlice, chessTile, chessIndiceBroad, chessIndice,   chessIter,         chessRep,      chessCstyle)
fundesc = ("Resize",    "Slicing",  "Tiling",  "CoordBroad"    , "Coordinates", "List processing", "Replication", "C style")
for fun,fundesc in zip(funlist,fundesc):
    print(fundesc)
    %timeit f = fun(H,W)


"""
Explanation: Comparing execution times
We compare the speed efficiency of the implementation above. We can see that slicing, tiling and resize are the most efficient
implementations because it is intrinsic to Numpy. Slicing is very efficient and is very recommending in most image processing
manipulation. Coordinate processing and replication have medium efficiency with coordinate processing being a very general
way to create images. Lastly list processing and C style programming present the worst efficiency because of its explicit
pixel scanning. For comparison, the result of the chess image is always of an array of type uint8, except for the coordinates method
where the result is in uint16 as the coordinate values are greater than 255.
End of explanation
"""


