Skip to content

Instantly share code, notes, and snippets.

@sadimanna
Last active May 4, 2024 08:11
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save sadimanna/52c320ce5c49e200ce398f800d39a2c1 to your computer and use it in GitHub Desktop.
Save sadimanna/52c320ce5c49e200ce398f800d39a2c1 to your computer and use it in GitHub Desktop.
Contrast Limited Adaptive Histogram Equalization in Python
'''
"Contrast Limited Adaptive Histogram Equalization"
by Karel Zuiderveld, karel@cv.ruu.nl
in "Graphics Gems IV", Academic Press, 1994
_Author_ -- Siladittya Manna
The below implementation does not assume that the
X- and Y image resolutions are an integer multiple
of the X- and Y sizes of the contextual regions.
Instead it pads the image with the number of excess pixels
required to make the X and Y resolutions an integral multiple
of X and Y sizes of the contextual region(if required)
Minimum and Maximum values are assued to be 0 and 255
respectively even if they are not present in the image
'''
import numpy as np
import matplotlib.pyplot as plt
from skimage import io
#INTERPOLATION FUNCTION
def interpolate(subBin,LU,RU,LB,RB,subX,subY):
subImage = np.zeros(subBin.shape)
num = subX*subY
for i in range(subX):
inverseI = subX-i
for j in range(subY):
inverseJ = subY-j
val = subBin[i,j].astype(int)
subImage[i,j] = np.floor((inverseI*(inverseJ*LU[val] + j*RU[val])+ i*(inverseJ*LB[val] + j*RB[val]))/float(num))
return subImage
#CLAHE FUNCTION
#ALL UTILITY FUNCTIONS COMBINED INTO ONE FUNCTION
def clahe(img,clipLimit,nrBins=128,nrX=0,nrY=0):
'''img - Input image
clipLimit - Normalized clipLimit. Higher value gives more contrast
nrBins - Number of graylevel bins for histogram("dynamic range")
nrX - Number of contextial regions in X direction
nrY - Number of Contextial regions in Y direction'''
h,w = img.shape
if clipLimit==1:
return
nrBins = max(nrBins,128)
if nrX==0:
#Taking dimensions of each contextial region to be a square of 32X32
xsz = 32
ysz = 32
nrX = np.ceil(h/xsz).astype(int)#240
#Excess number of pixels to get an integer value of nrX and nrY
excX= int(xsz*(nrX-h/xsz))
nrY = np.ceil(w/ysz).astype(int)#320
excY= int(ysz*(nrY-w/ysz))
#Pad that number of pixels to the image
if excX!=0:
img = np.append(img,np.zeros((excX,img.shape[1])).astype(int),axis=0)
if excY!=0:
img = np.append(img,np.zeros((img.shape[0],excY)).astype(int),axis=1)
else:
xsz = round(h/nrX)
ysz = round(w/nrY)
nrPixels = xsz*ysz
xsz2 = round(xsz/2)
ysz2 = round(ysz/2)
claheimg = np.zeros(img.shape)
if clipLimit > 0:
clipLimit = max(1,clipLimit*xsz*ysz/nrBins)
else:
clipLimit = 50
#makeLUT
print("...Make the LUT...")
minVal = 0 #np.min(img)
maxVal = 255 #np.max(img)
#maxVal1 = maxVal + np.maximum(np.array([0]),minVal) - minVal
#minVal1 = np.maximum(np.array([0]),minVal)
binSz = np.floor(1+(maxVal-minVal)/float(nrBins))
LUT = np.floor((np.arange(minVal,maxVal+1)-minVal)/float(binSz))
#BACK TO CLAHE
bins = LUT[img]
print(bins.shape)
#makeHistogram
print("...Making the Histogram...")
hist = np.zeros((nrX,nrY,nrBins))
print(nrX,nrY,hist.shape)
for i in range(nrX):
for j in range(nrY):
bin_ = bins[i*xsz:(i+1)*xsz,j*ysz:(j+1)*ysz].astype(int)
for i1 in range(xsz):
for j1 in range(ysz):
hist[i,j,bin_[i1,j1]]+=1
#clipHistogram
print("...Clipping the Histogram...")
if clipLimit>0:
for i in range(nrX):
for j in range(nrY):
nrExcess = 0
for nr in range(nrBins):
excess = hist[i,j,nr] - clipLimit
if excess>0:
nrExcess += excess
binIncr = nrExcess/nrBins
upper = clipLimit - binIncr
for nr in range(nrBins):
if hist[i,j,nr] > clipLimit:
hist[i,j,nr] = clipLimit
else:
if hist[i,j,nr]>upper:
nrExcess += upper - hist[i,j,nr]
hist[i,j,nr] = clipLimit
else:
nrExcess -= binIncr
hist[i,j,nr] += binIncr
if nrExcess > 0:
stepSz = max(1,np.floor(1+nrExcess/nrBins))
for nr in range(nrBins):
nrExcess -= stepSz
hist[i,j,nr] += stepSz
if nrExcess < 1:
break
#mapHistogram
print("...Mapping the Histogram...")
map_ = np.zeros((nrX,nrY,nrBins))
#print(map_.shape)
scale = (maxVal - minVal)/float(nrPixels)
for i in range(nrX):
for j in range(nrY):
sum_ = 0
for nr in range(nrBins):
sum_ += hist[i,j,nr]
map_[i,j,nr] = np.floor(min(minVal+sum_*scale,maxVal))
#BACK TO CLAHE
#INTERPOLATION
print("...interpolation...")
xI = 0
for i in range(nrX+1):
if i==0:
subX = int(xsz/2)
xU = 0
xB = 0
elif i==nrX:
subX = int(xsz/2)
xU = nrX-1
xB = nrX-1
else:
subX = xsz
xU = i-1
xB = i
yI = 0
for j in range(nrY+1):
if j==0:
subY = int(ysz/2)
yL = 0
yR = 0
elif j==nrY:
subY = int(ysz/2)
yL = nrY-1
yR = nrY-1
else:
subY = ysz
yL = j-1
yR = j
UL = map_[xU,yL,:]
UR = map_[xU,yR,:]
BL = map_[xB,yL,:]
BR = map_[xB,yR,:]
#print("CLAHE vals...")
subBin = bins[xI:xI+subX,yI:yI+subY]
#print("clahe subBin shape: ",subBin.shape)
subImage = interpolate(subBin,UL,UR,BL,BR,subX,subY)
claheimg[xI:xI+subX,yI:yI+subY] = subImage
yI += subY
xI += subX
if excX==0 and excY!=0:
return claheimg[:,:-excY]
elif excX!=0 and excY==0:
return claheimg[:-excX,:]
elif excX!=0 and excY!=0:
return claheimg[:-excX,:-excY]
else:
return claheimg
image = io.imread('example.jpg')
clahe_img = clahe(image[:,:,0],8,0,0)
#clipLimit = 8 gave decent results on eyePACs Dataset
#and setting xsz and ysz = 32
#and calculating nrX and nrY
fig,axs = plt.subplots(1,2,figsize=(200,100))
axs[0].imshow(image[:,:,0],cmap='gray')
axs[1].imshow(clahe_img,cmap='gray')
plt.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment