%%html
<script src="https://bits.csb.pitt.edu/preamble.js"></script>
import numpy as np; import matplotlib.pyplot as plt; import matplotlib.cm as cm
%matplotlib inline
from PIL import Image
im = Image.open('imgs/dogs.png')
im
As a reminder, we can use the point
method to apply an arbitrary function to each pixel.
pointed = im.point(lambda x: x+100)
pointed
A filter applies a convolution kernel to an image.
Although this sounds fancy, this is just a generalization of the point
method where the function takes as input the neighborhood of the pixels.
The kernel is represented by an $n$x$n$ matrix where the target pixel is in the center.
The output of the filter is the sum of the products of the matrix elements with the corresponding pixels.
Examples (from Wikipedia):
Identity | Blur | Edge Detection |
%%html
<div id="im2ef" style="width: 500px"></div>
<script>
var divid = '#im2ef';
jQuery(divid).asker({
id: divid,
question: "If a pixel and all its neighbors are the same color, how will its color change after applying the edge detection filter?",
answers: ['Same','Brighter','Darker','Black',"White"],
server: "https://bits.csb.pitt.edu/asker.js/example/asker.cgi",
charter: chartmaker})
$(".jp-InputArea .o:contains(html)").closest('.jp-InputArea').hide();
</script>
The PIL ImageFilter module includes a number of built-in convolution kernels that can be applied using the image filter
method.
from PIL import ImageFilter
print(ImageFilter.SMOOTH.filterargs) #shape, denominator, offset, coefficients
((3, 3), 13, 0, (1, 1, 1, 1, 5, 1, 1, 1, 1))
filt = ImageFilter.SMOOTH.filterargs
print(np.array(filt[3]).reshape(filt[0]),filt[1])
[[1 1 1] [1 5 1] [1 1 1]] 13
The output is the sum of the product of the coefficients and the corresponding pixels divided by the denominator and incremented by the offset.
def twoimgs(im1,im2):
'''Return the result of putting two images next to each other'''
w = im1.width+im2.width
h = im1.height
im = Image.new('RGB', (w, h))
im.paste(im1, (0,0))
im.paste(im2, (im1.width,0))
return im
filt = ImageFilter.BLUR.filterargs
print(np.array(filt[3]).reshape(filt[0]),filt[1])
twoimgs(im,im.filter(ImageFilter.BLUR))
[[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]] 16
filt = ImageFilter.SMOOTH.filterargs
print(np.array(filt[3]).reshape(filt[0]),filt[1])
twoimgs(im,im.filter(ImageFilter.SMOOTH))
[[1 1 1] [1 5 1] [1 1 1]] 13
filt = ImageFilter.FIND_EDGES.filterargs
print(np.array(filt[3]).reshape(filt[0]),filt[1])
[[-1 -1 -1] [-1 8 -1] [-1 -1 -1]] 1
twoimgs(im,im.filter(ImageFilter.FIND_EDGES))
filt = ImageFilter.SHARPEN.filterargs
print(np.array(filt[3]).reshape(filt[0]),filt[1])
twoimgs(im,im.filter(ImageFilter.SHARPEN))
[[-2 -2 -2] [-2 32 -2] [-2 -2 -2]] 16
There are also non-linear filters that don't compute sums of products. Instead, they compare the values of the neighboring pixels (e.g., max value, min value, median value).
twoimgs(im,im.filter(ImageFilter.MaxFilter(5)))
Recursion is when a function calls itself on a small version of the problem to compute the answer.
It is a very useful way to think about complex, but decomposable, problems.
def fib(n):
if n <= 1: return 1
return fib(n-1)+fib(n-2)
fib(5)
8
A recursive function must have a base case, an input that is eventually reached that does not require any further calls to the function ($n \le 1$ above).
What happens if you forget the base case?
def brokenfib(n):
return brokenfib(n-1)+brokenfib(n-2)
brokenfib(5)
--------------------------------------------------------------------------- RecursionError Traceback (most recent call last) Cell In[16], line 4 1 def brokenfib(n): 2 return brokenfib(n-1)+brokenfib(n-2) ----> 4 brokenfib(5) Cell In[16], line 2, in brokenfib(n) 1 def brokenfib(n): ----> 2 return brokenfib(n-1)+brokenfib(n-2) Cell In[16], line 2, in brokenfib(n) 1 def brokenfib(n): ----> 2 return brokenfib(n-1)+brokenfib(n-2) [... skipping similar frames: brokenfib at line 2 (2970 times)] Cell In[16], line 2, in brokenfib(n) 1 def brokenfib(n): ----> 2 return brokenfib(n-1)+brokenfib(n-2) RecursionError: maximum recursion depth exceeded
Every time you call a function, the function and its arguments are placed on the call stack.
The call stack will eventually run out of memory.
Python limits how many calls can be on the call stack.
import sys
sys.getrecursionlimit()
3000
sys.setrecursionlimit(6000) #broken fib will now take twice as long to crash
if n <= 1: return 1
)n-1
, n-2
)fib(n-1)
, fib(n-2)
)fib(n-1)+fib(n-2)
)def fun(L):
val = L[0]
if len(L) == 1: return val
val2 = fun(L[1:])
if val > val2: return val
return val2
%%html
<div id="im2rec" style="width: 500px"></div>
<script>
var divid = '#im2rec';
jQuery(divid).asker({
id: divid,
question: "What is the return value of <tt>fun([9,4,6,1,3,10,2])</tt>?",
answers: ['9','4','35','1','3','10','2','Error'],
server: "https://bits.csb.pitt.edu/asker.js/example/asker.cgi",
charter: chartmaker})
$(".jp-InputArea .o:contains(html)").closest('.jp-InputArea').hide();
</script>
Click on a pixel, fill that pixel and all touching pixels of the same color with the fill color.
Flood-fill (pixel, target-color, replacement-color)
What is/are the recursive step(s)?
What is the base case?
Flood-fill (pixel, target-color, replacement-color):
!wget http://bits.csb.pitt.edu/images/image1.tif
--2023-10-30 20:44:07-- http://bits.csb.pitt.edu/images/image1.tif Resolving bits.csb.pitt.edu (bits.csb.pitt.edu)... 136.142.4.139 Connecting to bits.csb.pitt.edu (bits.csb.pitt.edu)|136.142.4.139|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 921814 (900K) [image/tiff] Saving to: ‘image1.tif.1’ image1.tif.1 100%[===================>] 900.21K --.-KB/s in 0.02s 2023-10-30 20:44:07 (55.5 MB/s) - ‘image1.tif.1’ saved [921814/921814]
im = Image.open('image1.tif')
(r,g,b) = im.split()
b
The idea is to threshold the image and then count the number of white blobs.
blobs = b.point(lambda x: 255 if x > 45 else 0)
blobs
load
blobs = b.point(lambda x: 255 if x > 45 else 0)
pixels = blobs.load()
def flood(x,y,w,h,pixels):
pass #implement