Gabors / Primary Visual Cortex “Simple Cells” from Lena

How to build a (bio-plausible) “sparse” dictionary (or ‘codebook’, or ‘filterbank’) for e.g. image classification without any fancy math and with just standard python scientific libraries?

Please find below a short answer ;-)

This simple example shows how to get Gabor-like filters [1] using just the famous Lena image. Gabor filters are good approximations of the “Simple Cells” [2] receptive fields [3] found in the mammalian primary visual cortex (V1) (for details, see e.g. the Nobel-prize winning work of Hubel & Wiesel done in the 60s [4] [5]).

Here we use McQueen’s ‘kmeans’ algorithm [6], as a simple biologically plausible hebbian-like learning rule and we apply it (a) to patches of the original Lena image (retinal projection), and (b) to patches of an LGN-like [7] Lena image using a simple difference of gaussians (DoG) approximation.

Enjoy ;-) And keep in mind that getting Gabors on natural image patches is not rocket science.

[1]http://en.wikipedia.org/wiki/Gabor_filter
[2]http://en.wikipedia.org/wiki/Simple_cell
[3]http://en.wikipedia.org/wiki/Receptive_field
[4]http://en.wikipedia.org/wiki/K-means_clustering
[5]http://en.wikipedia.org/wiki/Lateral_geniculate_nucleus
[6]D. H. Hubel and T. N., Wiesel Receptive Fields of Single Neurones in the Cat’s Striate Cortex, J. Physiol. pp. 574-591 (148) 1959
[7]D. H. Hubel and T. N., Wiesel Receptive Fields, Binocular Interaction, and Functional Architecture in the Cat’s Visual Cortex, J. Physiol. 160 pp. 106-154 1962
../_images/plot_gabors_from_lena_1.png

import numpy as np
from scipy.cluster.vq import kmeans2
from scipy import ndimage as ndi
import matplotlib.pyplot as plt

from skimage import data
from skimage import color
from skimage.util.shape import view_as_windows
from skimage.util.montage import montage2d

np.random.seed(42)

patch_shape = 8, 8
n_filters = 49

lena = color.rgb2gray(data.lena())

# -- filterbank1 on original Lena
patches1 = view_as_windows(lena, patch_shape)
patches1 = patches1.reshape(-1, patch_shape[0] * patch_shape[1])[::8]
fb1, _ = kmeans2(patches1, n_filters, minit='points')
fb1 = fb1.reshape((-1,) + patch_shape)
fb1_montage = montage2d(fb1, rescale_intensity=True)

# -- filterbank2 LGN-like Lena
lena_dog = ndi.gaussian_filter(lena, .5) - ndi.gaussian_filter(lena, 1)
patches2 = view_as_windows(lena_dog, patch_shape)
patches2 = patches2.reshape(-1, patch_shape[0] * patch_shape[1])[::8]
fb2, _ = kmeans2(patches2, n_filters, minit='points')
fb2 = fb2.reshape((-1,) + patch_shape)
fb2_montage = montage2d(fb2, rescale_intensity=True)

# --
fig, axes = plt.subplots(2, 2, figsize=(7, 6))
ax0, ax1, ax2, ax3 = axes.ravel()

ax0.imshow(lena, cmap=plt.cm.gray)
ax0.set_title("Lena (original)")

ax1.imshow(fb1_montage, cmap=plt.cm.gray, interpolation='nearest')
ax1.set_title("K-means filterbank (codebook)\non Lena (original)")

ax2.imshow(lena_dog, cmap=plt.cm.gray)
ax2.set_title("Lena (LGN-like DoG)")

ax3.imshow(fb2_montage, cmap=plt.cm.gray, interpolation='nearest')
ax3.set_title("K-means filterbank (codebook)\non Lena (LGN-like DoG)")

for ax in axes.ravel():
    ax.axis('off')

fig.subplots_adjust(hspace=0.3)
plt.show()

STDOUT


        

STDERR


        

Python source code: download (generated using skimage 0.11dev)

IPython Notebook: download (generated using skimage 0.11dev)

aW1wb3J0IG51bXB5IGFzIG5wCmZyb20gc2NpcHkuY2x1c3Rlci52cSBpbXBvcnQga21lYW5zMgpmcm9tIHNjaXB5IGltcG9ydCBuZGltYWdlIGFzIG5kaQppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0Cgpmcm9tIHNraW1hZ2UgaW1wb3J0IGRhdGEKZnJvbSBza2ltYWdlIGltcG9ydCBjb2xvcgpmcm9tIHNraW1hZ2UudXRpbC5zaGFwZSBpbXBvcnQgdmlld19hc193aW5kb3dzCmZyb20gc2tpbWFnZS51dGlsLm1vbnRhZ2UgaW1wb3J0IG1vbnRhZ2UyZAoKbnAucmFuZG9tLnNlZWQoNDIpCgpwYXRjaF9zaGFwZSA9IDgsIDgKbl9maWx0ZXJzID0gNDkKCmxlbmEgPSBjb2xvci5yZ2IyZ3JheShkYXRhLmxlbmEoKSkKCiMgLS0gZmlsdGVyYmFuazEgb24gb3JpZ2luYWwgTGVuYQpwYXRjaGVzMSA9IHZpZXdfYXNfd2luZG93cyhsZW5hLCBwYXRjaF9zaGFwZSkKcGF0Y2hlczEgPSBwYXRjaGVzMS5yZXNoYXBlKC0xLCBwYXRjaF9zaGFwZVswXSAqIHBhdGNoX3NoYXBlWzFdKVs6OjhdCmZiMSwgXyA9IGttZWFuczIocGF0Y2hlczEsIG5fZmlsdGVycywgbWluaXQ9J3BvaW50cycpCmZiMSA9IGZiMS5yZXNoYXBlKCgtMSwpICsgcGF0Y2hfc2hhcGUpCmZiMV9tb250YWdlID0gbW9udGFnZTJkKGZiMSwgcmVzY2FsZV9pbnRlbnNpdHk9VHJ1ZSkKCiMgLS0gZmlsdGVyYmFuazIgTEdOLWxpa2UgTGVuYQpsZW5hX2RvZyA9IG5kaS5nYXVzc2lhbl9maWx0ZXIobGVuYSwgLjUpIC0gbmRpLmdhdXNzaWFuX2ZpbHRlcihsZW5hLCAxKQpwYXRjaGVzMiA9IHZpZXdfYXNfd2luZG93cyhsZW5hX2RvZywgcGF0Y2hfc2hhcGUpCnBhdGNoZXMyID0gcGF0Y2hlczIucmVzaGFwZSgtMSwgcGF0Y2hfc2hhcGVbMF0gKiBwYXRjaF9zaGFwZVsxXSlbOjo4XQpmYjIsIF8gPSBrbWVhbnMyKHBhdGNoZXMyLCBuX2ZpbHRlcnMsIG1pbml0PSdwb2ludHMnKQpmYjIgPSBmYjIucmVzaGFwZSgoLTEsKSArIHBhdGNoX3NoYXBlKQpmYjJfbW9udGFnZSA9IG1vbnRhZ2UyZChmYjIsIHJlc2NhbGVfaW50ZW5zaXR5PVRydWUpCgojIC0tCmZpZywgYXhlcyA9IHBsdC5zdWJwbG90cygyLCAyLCBmaWdzaXplPSg3LCA2KSkKYXgwLCBheDEsIGF4MiwgYXgzID0gYXhlcy5yYXZlbCgpCgpheDAuaW1zaG93KGxlbmEsIGNtYXA9cGx0LmNtLmdyYXkpCmF4MC5zZXRfdGl0bGUoIkxlbmEgKG9yaWdpbmFsKSIpCgpheDEuaW1zaG93KGZiMV9tb250YWdlLCBjbWFwPXBsdC5jbS5ncmF5LCBpbnRlcnBvbGF0aW9uPSduZWFyZXN0JykKYXgxLnNldF90aXRsZSgiSy1tZWFucyBmaWx0ZXJiYW5rIChjb2RlYm9vaylcbm9uIExlbmEgKG9yaWdpbmFsKSIpCgpheDIuaW1zaG93KGxlbmFfZG9nLCBjbWFwPXBsdC5jbS5ncmF5KQpheDIuc2V0X3RpdGxlKCJMZW5hIChMR04tbGlrZSBEb0cpIikKCmF4My5pbXNob3coZmIyX21vbnRhZ2UsIGNtYXA9cGx0LmNtLmdyYXksIGludGVycG9sYXRpb249J25lYXJlc3QnKQpheDMuc2V0X3RpdGxlKCJLLW1lYW5zIGZpbHRlcmJhbmsgKGNvZGVib29rKVxub24gTGVuYSAoTEdOLWxpa2UgRG9HKSIpCgpmb3IgYXggaW4gYXhlcy5yYXZlbCgpOgogICAgYXguYXhpcygnb2ZmJykKCmZpZy5zdWJwbG90c19hZGp1c3QoaHNwYWNlPTAuMykKcGx0LnNob3coKQ==