```python
import cv2
import pandas as pd
import math
import numpy as np
import sympy as sp
from tabulate import tabulate

from IPython.display import display, Latex
sp.init_printing()
```

## Utility Functions

```python
def pprint_df(dframe):
    print(tabulate(dframe, headers='keys', tablefmt='psql', showindex=True))

def clamp(min_v, max_v, val):
  return min(max(val, min_v), max_v)

def r(x, i=2):
    return str(round(x, i))

def calc_intensity(red, green, blue):
    return round((blue + green + red) / 3)

def calc_saturation(red, green, blue):
    minimum = min(red, green, blue)
    return round((1 - (3 / (red + green + blue + 0.001) * minimum))*255)

def calc_hue(red, green, blue):
    minv = min(red, green, blue)
    maxv = max(red, green, blue)
    if minv == maxv:
        return 0
    hue = 0
    if maxv == red:
        hue = (green - blue) / (maxv - minv)
    elif maxv == green:
        hue = 2 + (blue - red) / (maxv - minv)
    else:
        hue = 4 + (red - green) / (maxv - minv)
    hue = hue * 60;
    hue = hue + 360 if hue < 0 else hue;
    return round(hue)

def rgb_to_hsi(R, G, B):
  H = list(map(lambda x: calc_hue(R[x], G[x], B[x]), range(len(R))))
  S = list(map(lambda x: calc_saturation(R[x], G[x], B[x]), range(len(R))))
  I = list(map(lambda x: calc_intensity(R[x], G[x], B[x]), range(len(R))))
  return (H, S, I)

def hsi_to_rgb(H, S, I):
  def calc_rgb(hsi):
    h, s, i = hsi
    rad = math.pi / 180
    s = s/255
    i = i/255
    r, g, b = 0, 0, 0
    if h >= 0 and h < 120:
      r = i * (1 + ((s * math.cos(h * rad)) / math.cos((60 - h) * rad)))
      b = i * (1 - s)
      g = i * 3 - (r + b)
    elif h >= 120 and h < 240:
      h = h - 120
      g = i * (1 + ((s * math.cos(h * rad)) / math.cos((60 - h) * rad)))
      r = i * (1 - s)
      b = i * 3 - (r + g)
    else:
      h = h - 240
      b = i * (1 + ((s * math.cos(h * rad)) / math.cos((60 - h) * rad)))
      g = i * (1 - s)
      r = i * 3 - (b + g)
    return [round(clamp(0, 1, x) * 255) for x in (r, g, b)]

  return list(map(calc_rgb, list(zip(H, S, I))))

def calculate_hist(plane, L):
  hist = np.bincount(plane)
  pdf = hist / L
  cdf = np.cumsum(pdf)
  lookup = cdf * bit_rr

  df = pd.DataFrame({
      'count': hist.flatten(),
      'pdf': pdf.flatten(),
      'cdf': cdf.flatten(),
      'lookup': [clamp(0, 255, x) for x in lookup.flatten()]
      })
  df = df[df['count'] != 0]
  return df

def rescale_range(range, s_min, s_max):
  r_min = min(range)
  r_max = max(range)
  return list(map(lambda r:round(((((s_max - s_min)/(r_max - r_min)))*(r-r_min)+ s_min)), range))
```

# Range Scaling

```python
rescale_range([0, 10, 20, 30, 80, 100, 255], 175, 255)
```

# Bit Plane Slicing

```python
img = np.array([
    [255, 200, 150],
    [120, 140, 250],
    [20, 50, 20],
    [100, 50, 200]
], np.uint8)

bit_rate = 8;
bin_img = []
for i in range(img.shape[0]):
    for j in range(img.shape[1]):
         bin_img.append(np.binary_repr(img[i][j], width=bit_rate))

for plane in range(bit_rate):
  print(f'\n{plane+1} bit plane')
  sliced = (np.array([int(i[plane]) for i in bin_img], dtype = np.uint8)).reshape(img.shape[0],img.shape[1])
  reconstructed = (np.array([int(i[plane]) for i in bin_img], dtype = np.uint8) * 2 ** plane).reshape(img.shape[0],img.shape[1])
  pprint_df(pd.DataFrame(sliced))
  print(f'\n{plane+1} bit plane (reconstructed)')
  pprint_df(pd.DataFrame(reconstructed))

```

    1 bit plane
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   1 |   1 |   1 |
    |  1 |   0 |   1 |   1 |
    |  2 |   0 |   0 |   0 |
    |  3 |   0 |   0 |   1 |
    +----+-----+-----+-----+

    1 bit plane (reconstructed)
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   1 |   1 |   1 |
    |  1 |   0 |   1 |   1 |
    |  2 |   0 |   0 |   0 |
    |  3 |   0 |   0 |   1 |
    +----+-----+-----+-----+

    2 bit plane
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   1 |   1 |   0 |
    |  1 |   1 |   0 |   1 |
    |  2 |   0 |   0 |   0 |
    |  3 |   1 |   0 |   1 |
    +----+-----+-----+-----+

    2 bit plane (reconstructed)
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   2 |   2 |   0 |
    |  1 |   2 |   0 |   2 |
    |  2 |   0 |   0 |   0 |
    |  3 |   2 |   0 |   2 |
    +----+-----+-----+-----+

    3 bit plane
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   1 |   0 |   0 |
    |  1 |   1 |   0 |   1 |
    |  2 |   0 |   1 |   0 |
    |  3 |   1 |   1 |   0 |
    +----+-----+-----+-----+

    3 bit plane (reconstructed)
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   4 |   0 |   0 |
    |  1 |   4 |   0 |   4 |
    |  2 |   0 |   4 |   0 |
    |  3 |   4 |   4 |   0 |
    +----+-----+-----+-----+

    4 bit plane
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   1 |   0 |   1 |
    |  1 |   1 |   0 |   1 |
    |  2 |   1 |   1 |   1 |
    |  3 |   0 |   1 |   0 |
    +----+-----+-----+-----+

    4 bit plane (reconstructed)
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   8 |   0 |   8 |
    |  1 |   8 |   0 |   8 |
    |  2 |   8 |   8 |   8 |
    |  3 |   0 |   8 |   0 |
    +----+-----+-----+-----+

    5 bit plane
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   1 |   1 |   0 |
    |  1 |   1 |   1 |   1 |
    |  2 |   0 |   0 |   0 |
    |  3 |   0 |   0 |   1 |
    +----+-----+-----+-----+

    5 bit plane (reconstructed)
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |  16 |  16 |   0 |
    |  1 |  16 |  16 |  16 |
    |  2 |   0 |   0 |   0 |
    |  3 |   0 |   0 |  16 |
    +----+-----+-----+-----+

    6 bit plane
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   1 |   0 |   1 |
    |  1 |   0 |   1 |   0 |
    |  2 |   1 |   0 |   1 |
    |  3 |   1 |   0 |   0 |
    +----+-----+-----+-----+

    6 bit plane (reconstructed)
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |  32 |   0 |  32 |
    |  1 |   0 |  32 |   0 |
    |  2 |  32 |   0 |  32 |
    |  3 |  32 |   0 |   0 |
    +----+-----+-----+-----+

    7 bit plane
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   1 |   0 |   1 |
    |  1 |   0 |   0 |   1 |
    |  2 |   0 |   1 |   0 |
    |  3 |   0 |   1 |   0 |
    +----+-----+-----+-----+

    7 bit plane (reconstructed)
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |  64 |   0 |  64 |
    |  1 |   0 |   0 |  64 |
    |  2 |   0 |  64 |   0 |
    |  3 |   0 |  64 |   0 |
    +----+-----+-----+-----+

    8 bit plane
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 |   1 |   0 |   0 |
    |  1 |   0 |   0 |   0 |
    |  2 |   0 |   0 |   0 |
    |  3 |   0 |   0 |   0 |
    +----+-----+-----+-----+

    8 bit plane (reconstructed)
    +----+-----+-----+-----+
    |    |   0 |   1 |   2 |
    |----+-----+-----+-----|
    |  0 | 128 |   0 |   0 |
    |  1 |   0 |   0 |   0 |
    |  2 |   0 |   0 |   0 |
    |  3 |   0 |   0 |   0 |
    +----+-----+-----+-----+

# Dilation & Erosion

```python
img = np.array([
    [0, 0, 0, 0, 0, 0, 0],
    [0, 0, 1, 1, 0, 0, 0],
    [0, 1, 1, 1, 1, 0, 0],
    [0, 1, 1, 1, 1, 1, 0],
    [0, 1, 1, 1, 1, 0, 0],
    [0, 0, 1, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0],
], np.uint8)

kernel = np.array([
      [0, 1, 0],
      [1, 1, 1],
      [0, 1, 0]
], np.uint8)

img_dilation = cv2.dilate(img, kernel, iterations=1)
print('Dilated:')
pprint_df(pd.DataFrame(img_dilation))

img_erosion = cv2.erode(img, kernel, iterations=1)
print('\nEroded:')
pprint_df(pd.DataFrame(img_erosion))
```

    Dilated:
    +----+-----+-----+-----+-----+-----+-----+-----+
    |    |   0 |   1 |   2 |   3 |   4 |   5 |   6 |
    |----+-----+-----+-----+-----+-----+-----+-----|
    |  0 |   0 |   0 |   1 |   1 |   0 |   0 |   0 |
    |  1 |   0 |   1 |   1 |   1 |   1 |   0 |   0 |
    |  2 |   1 |   1 |   1 |   1 |   1 |   1 |   0 |
    |  3 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |
    |  4 |   1 |   1 |   1 |   1 |   1 |   1 |   0 |
    |  5 |   0 |   1 |   1 |   1 |   1 |   0 |   0 |
    |  6 |   0 |   0 |   1 |   1 |   0 |   0 |   0 |
    +----+-----+-----+-----+-----+-----+-----+-----+

    Eroded:
    +----+-----+-----+-----+-----+-----+-----+-----+
    |    |   0 |   1 |   2 |   3 |   4 |   5 |   6 |
    |----+-----+-----+-----+-----+-----+-----+-----|
    |  0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |
    |  1 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |
    |  2 |   0 |   0 |   1 |   1 |   0 |   0 |   0 |
    |  3 |   0 |   0 |   1 |   1 |   1 |   0 |   0 |
    |  4 |   0 |   0 |   1 |   1 |   0 |   0 |   0 |
    |  5 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |
    |  6 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |
    +----+-----+-----+-----+-----+-----+-----+-----+

# Internal & External Boundaries

```python
img = np.array([
   [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0],
    [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0],
    [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
    [0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0],
    [0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
], np.uint8)

kernel = np.array([
      [1, 1, 1],
      [1, 1, 1],
      [1, 1, 1]
], np.uint8)

img_external = (cv2.dilate(img, kernel, iterations=1) - img)
print('External:')
pprint_df(pd.DataFrame(img_external))

img_internal = (img - cv2.erode(img, kernel, iterations=1))
print('\nInternal:')
pprint_df(pd.DataFrame(img_internal))
```

    External:
    +----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+------+------+------+
    |    |   0 |   1 |   2 |   3 |   4 |   5 |   6 |   7 |   8 |   9 |   10 |   11 |   12 |   13 |
    |----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+------+------+------|
    |  0 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |    1 |    1 |    1 |    1 |
    |  1 |   1 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |    1 |    0 |    0 |    1 |
    |  2 |   1 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |    1 |    0 |    0 |    1 |
    |  3 |   1 |   1 |   1 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |    0 |    0 |    0 |    1 |
    |  4 |   1 |   0 |   0 |   0 |   0 |   0 |   1 |   0 |   0 |   0 |    0 |    0 |    0 |    1 |
    |  5 |   1 |   0 |   0 |   0 |   0 |   0 |   1 |   0 |   0 |   0 |    0 |    1 |    1 |    1 |
    |  6 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |    1 |    1 |    0 |    0 |
    +----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+------+------+------+

    Internal:
    +----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+------+------+------+
    |    |   0 |   1 |   2 |   3 |   4 |   5 |   6 |   7 |   8 |   9 |   10 |   11 |   12 |   13 |
    |----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+------+------+------|
    |  0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |    0 |    0 |    0 |    0 |
    |  1 |   0 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |   1 |    0 |    1 |    1 |    0 |
    |  2 |   0 |   1 |   1 |   1 |   0 |   0 |   0 |   0 |   0 |   1 |    0 |    1 |    1 |    0 |
    |  3 |   0 |   0 |   0 |   1 |   0 |   1 |   1 |   1 |   0 |   1 |    1 |    1 |    1 |    0 |
    |  4 |   0 |   1 |   1 |   1 |   0 |   1 |   0 |   1 |   0 |   0 |    1 |    1 |    1 |    0 |
    |  5 |   0 |   1 |   1 |   1 |   1 |   1 |   0 |   1 |   1 |   1 |    1 |    0 |    0 |    0 |
    |  6 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |   0 |    0 |    0 |    0 |    0 |
    +----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+------+------+------+

# Histogram Equalization

```python
img = np.array([
    [10, 25, 160, 10],
    [10, 80, 160, 25],
    [10, 80, 160, 25],
    [90, 80, 160, 90],
], np.uint8)

size = img.shape

bit_rate = 8;
bit_rr = 2**bit_rate
print('Range 0 -', bit_rr)

hist = cv2.calcHist([img], [0], None, [bit_rr], [0,bit_rr])
pdf = hist / (size[0] * size[1])
cdf = np.cumsum(pdf)
lookup = cdf * bit_rr

df = pd.DataFrame({
    'count': hist.flatten(),
    'pdf': pdf.flatten(),
    'cdf': cdf.flatten(),
    'lookup': lookup.flatten()
    })
df = df[df['count'] != 0]
pprint_df(df)

newImg = np.zeros(size)
for i in range(size[0]):
  for j in range(size[1]):
    newImg[i][j] = lookup[img[i][j]]

print('\nnew Image:');
pprint_df(newImg)
```

    Range 0 - 256
    +-----+---------+--------+--------+----------+
    |     |   count |    pdf |    cdf |   lookup |
    |-----+---------+--------+--------+----------|
    |  10 |       4 | 0.25   | 0.25   |       64 |
    |  25 |       3 | 0.1875 | 0.4375 |      112 |
    |  80 |       3 | 0.1875 | 0.625  |      160 |
    |  90 |       2 | 0.125  | 0.75   |      192 |
    | 160 |       4 | 0.25   | 1      |      256 |
    +-----+---------+--------+--------+----------+

    new Image:
    +----+-----+-----+-----+-----+
    |    |   0 |   1 |   2 |   3 |
    |----+-----+-----+-----+-----|
    |  0 |  64 | 112 | 256 |  64 |
    |  1 |  64 | 160 | 256 | 112 |
    |  2 |  64 | 160 | 256 | 112 |
    |  3 | 192 | 160 | 256 | 192 |
    +----+-----+-----+-----+-----+

# Colored Image Histogram Equalization

```python
# RGB Regions
regions = np.array([
    [250, 200, 150],
    [120, 140, 250],
    [40, 100, 40],
    [110, 50, 200]
])

# per plane
bit_rate = 8;
bit_rr = 2**bit_rate

R, G, B = list(zip(*regions))
```

## Equalization in RGB Space

```python
print('R Plane: ')
r_df = calculate_hist(R, len(regions))
pprint_df(r_df)

print('\nG Plane: ')
g_df= calculate_hist(G, len(regions))
pprint_df(g_df)

print('\nB Plane: ')
b_df = calculate_hist(B, len(regions))
pprint_df(b_df)

newR = list(map(lambda x: r_df.loc[[x]]['lookup'].to_numpy()[0], R))
newG = list(map(lambda x: g_df.loc[[x]]['lookup'].to_numpy()[0], G))
newB = list(map(lambda x: b_df.loc[[x]]['lookup'].to_numpy()[0], B))
print('\nRGB Equalized Regions:')
print(np.vstack(list(zip(newR, newG, newB))))
```

    R Plane:
    +-----+---------+-------+-------+----------+
    |     |   count |   pdf |   cdf |   lookup |
    |-----+---------+-------+-------+----------|
    |  40 |       1 |  0.25 |  0.25 |       64 |
    | 110 |       1 |  0.25 |  0.5  |      128 |
    | 120 |       1 |  0.25 |  0.75 |      192 |
    | 250 |       1 |  0.25 |  1    |      255 |
    +-----+---------+-------+-------+----------+

    G Plane:
    +-----+---------+-------+-------+----------+
    |     |   count |   pdf |   cdf |   lookup |
    |-----+---------+-------+-------+----------|
    |  50 |       1 |  0.25 |  0.25 |       64 |
    | 100 |       1 |  0.25 |  0.5  |      128 |
    | 140 |       1 |  0.25 |  0.75 |      192 |
    | 200 |       1 |  0.25 |  1    |      255 |
    +-----+---------+-------+-------+----------+

    B Plane:
    +-----+---------+-------+-------+----------+
    |     |   count |   pdf |   cdf |   lookup |
    |-----+---------+-------+-------+----------|
    |  40 |       1 |  0.25 |  0.25 |       64 |
    | 150 |       1 |  0.25 |  0.5  |      128 |
    | 200 |       1 |  0.25 |  0.75 |      192 |
    | 250 |       1 |  0.25 |  1    |      255 |
    +-----+---------+-------+-------+----------+

    RGB Equalized Regions:
    [[255. 255. 128.]
     [192. 192. 255.]
     [ 64. 128.  64.]
     [128.  64. 192.]]

## Equalization in HSI

```python
print('RGB values:')
print(np.vstack(regions))

# HSI
H, S, I = rgb_to_hsi(R, G, B)
print('\nRGB to HSI:')
print(np.vstack(list(zip(H, S, I))))

# equalize Intensity plane
hist = calculate_hist(I, len(regions))
print('\nEqualize Intensity: ')
pprint_df(hist)

newI = list(map(lambda x: hist.loc[[x]]['lookup'].to_numpy()[0], I))
print('\nNew Regions in HSI:')
print(np.vstack(list(zip(H, S, newI))))

print('\nNew Region in RGB:')
print(np.vstack(hsi_to_rgb(H, S, newI)))
```

    RGB values:
    [[250 200 150]
     [120 140 250]
     [ 40 100  40]
     [110  50 200]]

    RGB to HSI:
    [[ 30  64 200]
     [231  75 170]
     [120  85  60]
     [264 149 120]]

    Equalize Intensity:
    +-----+---------+-------+-------+----------+
    |     |   count |   pdf |   cdf |   lookup |
    |-----+---------+-------+-------+----------|
    |  60 |       1 |  0.25 |  0.25 |       64 |
    | 120 |       1 |  0.25 |  0.5  |      128 |
    | 170 |       1 |  0.25 |  0.75 |      192 |
    | 200 |       1 |  0.25 |  1    |      255 |
    +-----+---------+-------+-------+----------+

    New Regions in HSI:
    [[ 30.  64. 255.]
     [231.  75. 192.]
     [120.  85.  64.]
     [264. 149. 128.]]

    New Region in RGB:
    [[255 255 191]
     [136 160 255]
     [ 43 107  43]
     [118  53 212]]

# Nearest Neighbour Interplotation

```python
def nni(image, point):
  max_x = round(point[0])
  max_y = round(point[1])

  print(f"x: {max_x}, y: {max_y}")
  return image[max_x, max_y]

```

```python
img = np.array([
    [10, 30],
    [20, 90],
], np.uint8)

# NOTE: convert matlab index to python index (ie python index starts from 0 inseat of 1)
val = nni(img, (0.5, 0.6))
print(f'val: {val}')
```

    x: 0, y: 1
    val: 30

# Bilinear Interplotaion

```python
def bli(img, point):
  point = point[1], point[0]
  x = math.floor(point[0])
  y = math.floor(point[1])

  x_offset = point[0] - x
  y_offset = point[1] - y

  print(f'{img[x, y]}  {img[x, y+1]}')
  print(f'{img[x+1, y]}  {img[x+1, y+1]}')

  ix1 = (1-x_offset) * img[x, y] + (x_offset) * img[x, y+1]
  ix2 = (1-x_offset) * img[x+1, y] + (x_offset) * img[x+1, y+1]
  p = y_offset * ix2 + (1-y_offset) * ix1

  print("")
  display(Latex(f'I_{{({x}, {point[1]})}} = \\frac{{{x+1} - {point[0]}}}{{{x+1} - {x}}} \\cdotp {img[x, y]} + \\frac{{{point[0]} - {x}}}{{{x+1} - {x}}} \\cdotp {img[x, y+1]} = {r(ix1)}'))
  print("")
  display(Latex(f'I_{{({x+1}, {point[1]})}} = \\frac{{{x+1} - {point[0]}}}{{{x+1} - {x}}} \\cdotp {img[x+1, y]} + \\frac{{{point[0]} - {x}}}{{{x+1} - {x}}} \\cdotp {img[x+1, y+1]} = {r(ix2)}'))
  print("")
  display(Latex(f'I_{{({point[0]}, {point[1]})}} = \\frac{{{y+1} - {point[1]}}}{{{y+1} - {y}}} \\cdotp {r(ix1)} + \\frac{{{point[1]} - {y}}}{{{y+1} - {y}}} \\cdotp {r(ix2)} = {r(p)}'))
```

```python
def bli2(img, point):
  x = math.floor(point[0])
  y = math.floor(point[1])

  x_offset = point[0] - x
  y_offset = point[1] - y

  print(f'{img[x, y]}  {img[x, y+1]}')
  print(f'{img[x+1, y]}  {img[x+1, y+1]}')

  print(f'= [({r(1-x_offset)} * {img[x, y]}) + ({r(x_offset)} * {img[x+1, y]})] * {r(1-y_offset)} + [({r(1-x_offset)} * {img[x, y+1]}) + ({r(x_offset)} * {img[x+1, y+1]})] * {r(y_offset)}')
  print(f'= [{r((1-x_offset) * img[x, y])} + {r((x_offset) * img[x+1, y])}] * {r(1-y_offset)} + [{r((1-x_offset) * img[x, y+1])} + {r((x_offset) * img[x+1, y+1])}] * {r(y_offset)}')
  print(f'= {r((1-x_offset) * img[x, y] + (x_offset) * img[x+1, y])} * {r(1-y_offset)} + {r((1-x_offset) * img[x, y+1] + (x_offset) * img[x+1, y+1])} * {r(y_offset)}')

  print(f'= {r((((1-x_offset) * img[x, y] + (x_offset) * img[x+1, y]) * (1-y_offset)) + (((1-x_offset) * img[x, y+1] + (x_offset) * img[x+1, y+1]) * (y_offset)))}')
```

```python
img = np.array([
    [10, 40, 3],
    [80, 90, 3],
    [2, 2, 1],
], np.uint8)

# NOTE: convert matlab index to python index (ie python index starts from 0 inseat of 1)
bli(img, (0.2, 0.8))
# bli(img, (0.2, 0.5))
# bli(img, (0.5, 0.2))

# bli2(img, (0.2, 0.5))
# bli2(img, (1.6, 1.7))
```

    10  40
    80  90

I_{(0, 0.2)} = \frac{1 - 0.8}{1 - 0} \cdotp 10 + \frac{0.8 - 0}{1 - 0} \cdotp 40 = 34.0

I_{(1, 0.2)} = \frac{1 - 0.8}{1 - 0} \cdotp 80 + \frac{0.8 - 0}{1 - 0} \cdotp 90 = 88.0

I_{(0.8, 0.2)} = \frac{1 - 0.2}{1 - 0} \cdotp 34.0 + \frac{0.2 - 0}{1 - 0} \cdotp 88.0 = 44.8

# Color Models

## RGB to HSI

```python
def rgb2hsi(r, g, b):
  re, ge, be = sp.symbols('r g b')

  ie = sp.simplify("1/3") * (re + ge + be)
  se = sp.simplify("1") - 1/ie * min(r, g, b)
  te = sp.acos((1/2*((re-ge) + (re-be))) / sp.sqrt((re- ge)**2 + (re-be) * (ge-be)))

  display(sp.Eq(sp.symbols('I'), ie))
  print("")
  display(sp.Eq(sp.symbols('S'), se))
  print("")
  display(sp.Eq(sp.symbols('theta'), te))

  i = sp.lambdify((re, ge, be), ie)(r, g, b)
  s = sp.lambdify((re, ge, be), se)(r, g, b)
  t = sp.lambdify((re, ge, be), te)(r, g, b)
  t = 0 if np.isnan(t) else t
  h = t if b <= g else 2*math.pi - t

  display(sp.Eq(sp.symbols('theta'), t))
  display(sp.Eq(sp.symbols('H'), h))
  display(sp.Eq(sp.symbols('S'), s))
  display(sp.Eq(sp.symbols('I'), i))

  return h * 180 / math.pi, s*255, i
```

```python
h, s, i = rgb2hsi(100, 50, 200)
print("")
print(r(h, 0), r(s, 0), r(i, 0))
```

## HSI to RGB

```python
def hsi2rgb(h, s, i):
  he, se, ie = sp.symbols('H S I')
  rs, gs, bs = sp.symbols('R G B')

  h = h * math.pi / 180
  print(h, s, i)

  if h >= 0 and h < 2 * math.pi / 3:
    display("h >= 0 and h < 120")

    re = ie * (1 + ((se * sp.cos(he)) / sp.cos(sp.pi/3 - he)))
    be = ie * (1 - se)
    ge = 3 * ie - (rs + bs)

    r = sp.lambdify((he, se, ie), re)(h, s, i)
    b = sp.lambdify((he, se, ie), be)(h, s, i)
    g = sp.lambdify((rs, bs, ie), ge)(r, b, i)

  elif h >= 2 * math.pi / 3 and h < 4 * math.pi / 3:
    display("h >= 120 and h < 240")
    h = h - 2 * math.pi / 3

    re = ie * (1 - se)
    ge = ie * (1 + ((se * sp.cos(he)) / sp.cos(sp.pi/3 - he)))
    be = 3 * ie - (rs + gs)

    r = sp.lambdify((he, se, ie), re)(h, s, i)
    g = sp.lambdify((he, se, ie), ge)(h, s, i)
    b = sp.lambdify((rs, gs, ie), be)(r, g, i)
  else:
    display("h >= 240 and h < 360")
    h = h - 4 * math.pi / 3

    be = ie * (1 + ((se * sp.cos(he)) / sp.cos(sp.pi/3 - he)))
    ge = ie * (1 - se)
    re = 3 * ie - (gs + bs)

    b = sp.lambdify((he, se, ie), be)(h, s, i)
    g = sp.lambdify((he, se, ie), ge)(h, s, i)
    r = sp.lambdify((gs, bs, ie), re)(g, b, i)

  display(sp.Eq(rs, re))
  print("")
  display(sp.Eq(gs, ge))
  print("")
  display(sp.Eq(bs, be))
  print("")

  display(sp.Eq(rs, r ))
  print("")
  display(sp.Eq(gs, g ))
  print("")
  display(sp.Eq(bs, b))
  print("")

  return [round(clamp(0, 1, x) * 255) for x in (r, g, b)]
```

```python
hsi2rgb(260, 146/255, 117/255)
```

# Distance Measure

```python
def euclidean_distance(p, q):
  d = math.sqrt((p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2)

  display(Latex('D_e(p, q) = \\sqrt{(x - s)^2 + (y - t)^2}'))
  display(Latex(f'D_e(p, q) = \\sqrt{{({p[0]} - {q[0]})^2 + ({p[1]} - {q[1]})^2}}'))
  display(Latex(f'D_e(p, q) = \\sqrt{{{(p[0] - q[0]) ** 2} + {(p[1] - q[1]) ** 2}}}'))
  display(Latex(f'D_e(p, q) = {r(d)}'))
  return d
```

```python
euclidean_distance((10, 20), (12, 22))
```

```python
def city_block_distance(p, q):
  d = abs(p[0] - q[0]) + abs(p[1] - q[1])

  display(Latex('D_4(p, q) = \\left|x - s\\right| + \\left|y - t\\right|'))
  display(Latex(f'D_4(p, q) = \\left|{p[0]} - {q[0]}\\right| + \\left|{p[1]} - {q[1]}\\right|'))
  display(Latex(f'D_4(p, q) = {abs(p[0] - q[0])} + {abs(p[1] - q[1])}'))
  display(Latex(f'D_4(p, q) = {r(d)}'))
  return d
```

```python
city_block_distance((10, 20), (12, 22))
```

```python
def chess_board_distance(p, q):
  d = max(abs(p[0] - q[0]), abs(p[1] - q[1]))

  display(Latex('D_8(p, q) = max\\left(\\left|x - s\\right|, \\left|y - t\\right|\\right)'))
  display(Latex(f'D_8(p, q) = max\\left(\\left|{p[0]} - {q[0]}\\right|, \\left|{p[1]} - {q[1]}\\right|\\right)'))
  display(Latex(f'D_8(p, q) = max\\left({abs(p[0] - q[0])}, {abs(p[1] - q[1])}\\right)'))
  display(Latex(f'D_8(p, q) = {r(d)}'))
  return d
```

```python
chess_board_distance((10, 20), (12, 22))
```

```python

```


```python
import random
import matplotlib.pyplot as plt
import numpy as np
import sympy
from sympy import *
```

```python
MSE = []
solved = 0
unsolved = 0

for ran_idx in range(100):
    try:
        random.seed(ran_idx)
        np.random.seed(ran_idx)
        source = [random.uniform(-3,3),random.uniform(0,5)]
        mic1 = [-2,1]
        mic2 = [0,0]
        mic3 = [2,1]

        locations = np.zeros([4,2])
        locations[0] = source
        locations[1] = mic1
        locations[2] = mic2
        locations[3] = mic3

        mu = np.array([0,0,0])
        sigma = np.array([0.1,0.1,0.1])

        noise = np.random.normal(mu,sigma)

        d1 = np.sqrt((locations[0,0]-locations[1,0])**2 + (locations[0,1]-locations[1,1])**2) + noise[0]
        d2 = np.sqrt((locations[0,0]-locations[2,0])**2 + (locations[0,1]-locations[2,1])**2) + noise[1]
        d3 = np.sqrt((locations[0,0]-locations[3,0])**2 + (locations[0,1]-locations[3,1])**2) + noise[2]

        d_diff = np.array([d1-d2,d1-d3,d2-d3])

        def f(x,y,idx):
            return ((x-locations[idx,0])**2 + (y-locations[idx,1])**2)**0.5

        x = Symbol('x')
        y = Symbol('y')

        try:
            soln = nsolve([f(x,y,1)-f(x,y,2)-d_diff[0], f(x,y,1)-f(x,y,3)-d_diff[1], f(x,y,2)-f(x,y,3)-d_diff[2]],[x,y],[-2.5,5])
        except:
            soln = nsolve([f(x,y,1)-f(x,y,2)-d_diff[0], f(x,y,1)-f(x,y,3)-d_diff[1], f(x,y,2)-f(x,y,3)-d_diff[2]],[x,y],[2.5,5])

        mse = (soln[0]-locations[0,0])**2 + (soln[1]-locations[0,1])**2

        if mse > 1:
            unsolved += 1
        else:
            solved += 1
            MSE.append(mse)

    except:
        #print('unable to solve')
        unsolved += 1
        continue

mean_MSE = np.mean(MSE)

print('Number of cases solved:', solved)
print('Number of unsolved cases:', unsolved)
```

    Number of cases solved: 75
    Number of unsolved cases: 25
    The mean MSE of solved cases is: 0.236490618429010

```python
x = np.linspace(-3.5,3.5,500)
y = np.linspace(-0.2,2.7,500)

X,Y = np.meshgrid(x,y)

plt.contour(X,Y,f(X,Y,1)-f(X,Y,2)-d_diff[0],0)
plt.contour(X,Y,f(X,Y,1)-f(X,Y,3)-d_diff[1],0)
plt.contour(X,Y,f(X,Y,2)-f(X,Y,3)-d_diff[2],0)
plt.scatter(soln[0], soln[1], label='Estimate source')
plt.scatter(locations[0,0], locations[0,1], label='True source')
plt.scatter(locations[1:,0], locations[1:,1], label='Microphones')

labels = ['True', 'm1', 'm2', 'm3']
plt.annotate('Estimation', (soln[0]+0.2, soln[1]-0.05))
plt.annotate(labels[0], (locations[0,0]+0.24, locations[0,1]-0.05))
for i in range(1,4):
    plt.annotate(labels[i], (locations[i,0], locations[i,1]+0.1))
plt.legend(loc='upper right')
plt.show()
```