# -*- coding: utf-8 -*-
from __future__ import division
from past.utils import old_div
import cv2
import numpy as np
from ..lib.arrayops import (normsigmoid, normalize, Bandpass, Bandstop,
findminima, findmaxima, find_near, smooth, getOtsuThresh, convexityRatio,
filterFactory, brightness, background, thresh_biggestCnt, contours2mask,
findContours)
def _getBrightAlpha(backgray, foregray, window=None):
"""
Get alpha transparency for merging foreground to
background gray image according to brightness.
(This is a test and not intended for practical use)
:param backgray: background image.
:param foregray: foreground image.
:param window: window used to customizing alfa. It can be a binary or alpha mask,
values go from 0 for transparency to any value where the maximum is visible
i.e a window with all the same values does nothing. A binary mask can be used,
where 0 is transparent and 1 is visible. If not window is given alfa is not
altered and the intended alpha is returned.
:return: alfa mask
"""
# this method was obtained for a particular problem, change to an
# automated one
backmask = normalize(normsigmoid(backgray, 10, 180) + normsigmoid(backgray, 3.14, 192) +
normsigmoid(backgray, -3.14, 45))
foremask = normalize(normsigmoid(foregray, -1, 242)
* normsigmoid(foregray, 3.14, 50))
foremask = normalize(foremask * backmask)
foremask[foremask > 0.9] = 2.0
ksize = (21, 21)
foremask = normalize(cv2.blur(foremask, ksize))
if window is not None:
foremask *= normalize(window) # ensures that window is normilized to 1
return foremask
[docs]def get_beta_params_hist(P):
"""
Automatically find parameters for bright alpha masks
using a histogram analysis method.
:param P: gray image
:return: beta1 for minimum valley left of body, beta2 for brightest valley right of body
where the body starts at the tallest peak in the histogram.
"""
hist_P, bins = np.histogram(P.flatten(), 256, [0, 256])
window = "hamming" # 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'
window_len = 51
hist_PS = smooth(hist_P, window_len, window=window, correct=True)
otsu = getOtsuThresh(hist_P) # Otsu value
minima = findminima(hist=hist_PS) # minima values
maxima = findmaxima(hist=hist_PS) # maxima values
if minima != [] and maxima != []:
data_body = find_near(maxima, thresh=otsu, side="right")
beta1 = find_near(minima, thresh=data_body, side="left")
beta2 = find_near(minima, thresh=data_body, side="right")
else: # case where histogram is uniform and does not have minima or maxima values
beta1 = beta2 = otsu
if beta2 == beta1:
beta2 += 1 # prevents overlapping
return beta1, beta2
[docs]def get_bright_alpha(backgray, foregray, window=None):
"""
Get alpha transparency for merging foreground to
background gray image according to brightness.
:param backgray: background image. (as float)
:param foregray: foreground image. (as float)
:param window: window used to customizing alfa. It can be a binary or alpha mask,
values go from 0 for transparency to any value where the maximum is visible
i.e a window with all the same values does nothing. A binary mask can be used,
where 0 is transparent and 1 is visible. If not window is given alfa is not
altered and the intended alpha is returned.
:return: alfa mask
"""
backmask = Bandpass(3, *get_beta_params_hist(backgray)
)(backgray) # beta1 = 50, beta2 = 190
foremask = Bandpass(3, *get_beta_params_hist(foregray)
)(foregray) # beta1 = 50, beta2 = 220
foremask = normalize(foremask * backmask)
if window is not None:
foremask *= normalize(window) # ensures that window is normilized to 1
return foremask
[docs]def get_beta_params_Otsu(P):
"""
Automatically find parameters for alpha masks using Otsu threshold value.
:param P: gray image
:return: beta1 for minimum histogram value, beta2 for Otsu value
"""
# process histogram for uint8 gray image
if P.any(): # if array is not empty
hist, bins = np.histogram(P.flatten(), 256)
# get Otsu thresh as beta2
beta2 = bins[getOtsuThresh(hist)]
return np.min(P), beta2 # beta1, beta2
return 0.0, 1.0
[docs]def get_layered_alpha(back, fore):
"""
Get bright alpha mask (using Otsu method)
:param back: BGR background image
:param fore: BGR foreground image
:return: alpha mask
"""
# find retinal area and its alpha
mask_back, alpha_back = retinal_mask(back, biggest=True, addalpha=True)
mask_fore, alpha_fore = retinal_mask(fore, biggest=True, addalpha=True)
# convert uint8 to float
backgray = brightness(back).astype(float)
foregray = brightness(fore).astype(float)
# scale from 0-1 to 0-255
backm = alpha_back * 255
forem = alpha_fore * 255
# get alpha masks fro background and foreground
backmask = Bandstop(
3, *get_beta_params_Otsu(backm[mask_back.astype(bool)]))(backgray)
foremask = Bandpass(
3, *get_beta_params_Otsu(forem[mask_fore.astype(bool)]))(foregray)
# merge masks
alphamask = normalize(foremask * backmask * (old_div(backm, 255.)))
return alphamask
[docs]def retina_markers_thresh(P):
"""
Retinal markers thresholds to find background,
retinal area and optic disc with flares based
in the histogram.
:param P: gray image
:return: min,b1,b2,max
where:
black background < min
b1 > retina < b2
flares > max
"""
# calculate histogram
#hist_P = histogram(P)[0]
hist_P, bins = np.histogram(P.flatten(), 256, [0, 256])
# filter histogram to smooth it.
window = "hamming" # 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'
window_len = 51
# it smooths and expands the histogram
hist_PS = smooth(hist_P, window_len, window=window, correct=True)
#from scipy.signal import savgol_filter
#hist_PS = savgol_filter(hist_P, window_len, polyorder = 3)
otsu = getOtsuThresh(hist_P) # Otsu value
minima = findminima(hist=hist_PS) # minima values
maxima = findmaxima(hist=hist_PS) # maxima values
# INITIAL DATA
data_min_left = P.min() # initial minimum value
data_min = find_near(maxima, thresh=data_min_left)
#data_min_right = find_near(minima, thresh=data_min, side="right")
data_max = P.max()
data_max_left = find_near(minima, thresh=data_max, side="left")
# SECONDARY DATA
data_body = find_near(maxima, thresh=otsu, side="right")
data_body_left = find_near(minima, thresh=data_body, side="left")
#data_body_right = find_near(minima,thresh=data_body,side="right")
return data_min, data_body_left, data_body, data_max_left
[docs]def find_optic_disc_watershed(img, P):
"""
Find optic disk in image using a watershed method.
:param img: BGR image
:param P: gray image
:return: optic_disc, Crs, markers, watershed
"""
assert img is not None and P is not None
#fore = cv2.cvtColor(P,cv2.COLOR_GRAY2BGR)
# create watershed
data_min, data_body_left, data_body, data_max_left = retina_markers_thresh(
P)
watershed = np.zeros_like(P).astype("int32")
mk_back, mk_body, mk_flare = 1, 2, 3
# background FIXMED use P.min() and aproaching to local maxima
watershed[P <= data_min] = mk_back
watershed[np.bitwise_and(P > data_body_left, P <
data_body)] = mk_body # main body
# find bright objects
flares_thresh = (P >= data_max_left).astype(np.uint8)
contours, hierarchy = findContours(
flares_thresh.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
mks_flare = []
for i, cnt in enumerate(contours):
index = mk_flare + i
mks_flare.append(index)
# Flares. this can be used approaching
watershed[contours2mask(cnt, shape=watershed.shape)] = index
# to local maxima, but brightest areas are almost
# always saturated so no need to use it
markers = watershed.copy()
# apply watershed to watershed
# FIXME perhaps the function should be cv2.floodFill?
cv2.watershed(img, watershed)
# different classification algorithms could be used using watershed
contours_flares = []
for mk_flare in mks_flare:
brightest = np.uint8(watershed == mk_flare)
contours, hierarchy = findContours(
brightest, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours_flares.extend(contours)
Crs = [(i, j) for i, j in [(convexityRatio(cnt), cnt)
for cnt in contours_flares] if i != 0] # convexity ratios
Crs.sort(reverse=True, key=lambda x: x[0])
candidate = Crs[-1]
ellipse = cv2.fitEllipse(candidate[1])
optic_disc = np.zeros_like(P)
cv2.ellipse(optic_disc, ellipse, 1, -1) # get elliptical ROI
return optic_disc, Crs, markers, watershed
[docs]def layeredfloods(img, gray=None, backmask=None, step=1, connectivity=4, weight=False):
"""
Create an alpha mask from an image using a weighted layered flooding algorithm,
:param img: BGR image
:param gray: Gray image
:param backmask: background mask
:param step: step to increase upDiff in the floodFill algorithm. If weight is True
step also increases the weight of the layers.
:param connectivity: pixel connectivity of 4 or 8 to use in the floodFill algorithm
:param weight: Increase progressively the weight of the layers using the step parameter.
:return: alpha mask
"""
if gray is None:
if len(img.shape) > 2:
gray = brightness(img)
else:
gray = img
# initialization of flags for flooding
flags = connectivity | cv2.FLOODFILL_FIXED_RANGE | cv2.FLOODFILL_MASK_ONLY
h, w = gray.shape # get image shape
# pad mask to add 1 layer of pixels
# this is needed in the flood operation
mask = np.zeros((h + 2, w + 2), np.uint8)
# create the background mask
if backmask is None:
backmask = background(gray)
mask_background = np.zeros((h + 2, w + 2), np.uint8)
mask_background[1:-1, 1:-1] = backmask
# get area of background mask
area_background = np.sum(mask_background)
# low and high limits for flooding
lo, hi = 255, 0
# first seed point of the minimum color
xs, ys = np.where(gray == np.min(gray))
seed_pt = xs[0], ys[0]
# flood adding overal layers starting from background
area = 0
hi = step
all = np.zeros_like(mask, np.float32)
while hi <= 255:
mask[:] = 0
cv2.floodFill(img, mask, seed_pt, (255, 255, 255),
(lo,) * 3, (hi,) * 3, flags)
if weight and area >= area_background:
all += mask.astype("float") * hi
elif not weight and area >= area_background:
all += mask.astype("float")
else:
area = np.sum(np.bitwise_and(mask, mask_background))
hi += step
all = all[1:-1, 1:-1]
all /= all.max()
return all
[docs]def retinal_mask(img, biggest=False, addalpha=False):
"""
Obtain the mask of the retinal area in an image.
For a simpler and lightweight algorithm see :func:`retinal_mask_watershed`.
:param img: BGR or gray image
:param biggest: True to return only biggest object
:param addalpha: True to add additional alpha mask parameter
:return: if addalpha:
binary mask, alpha mask
else:
binary mask
"""
# pad image to add 1 layer of pixels
# this is used to correctly flood all the background
if len(img.shape) > 2:
h, w, c = img.shape
imgP = np.zeros((h + 2, w + 2, 3), np.uint8)
imgP[1:-1, 1:-1, :] = img
P = brightness(imgP)
else:
h, w = img.shape
imgP = np.zeros((h + 2, w + 2), np.uint8)
imgP[1:-1, 1:-1] = img
P = imgP
# get flooded alpha mask
mask_alpha = 1 - layeredfloods(imgP, gray=P)[1:-1, 1:-1]
hist, bins = np.histogram(mask_alpha.flatten(), 256)
thresh = bins[getOtsuThresh(hist)]
mask_binary = (mask_alpha > thresh).astype(np.uint8)
if biggest: # process to give only the biggest object
a = thresh_biggestCnt(mask_binary)
mask_binary = np.zeros_like(mask_binary)
cv2.drawContours(mask_binary, [a], 0, 1, -1) # get current fillet ROI
if addalpha:
#if biggest: mask_alpha[mask_binary==0] = 0
return mask_binary, mask_alpha
return mask_binary
[docs]def retinal_mask_watershed(img, parameters=(10, 30, None), addMarkers=False):
"""
Quick and simple watershed method to obtain the mask of the retinal area in an image.
For a more robust algorithm see :func:`retinal_mask`.
:param img: BGR or gray image
:param parameters: tuple of parameters to pass to :func:`filterFactory`
:param addMarkers: True to add additional Marker mask. It contains 0 for unknown
areas, 1 for background and 2 for retinal area.
:return: if addMarkers:
binary mask, Markers mask
else:
binary mask
"""
if parameters is not None:
myfilter = filterFactory(*parameters) # alfa,beta1,beta2
img = (myfilter(img.astype("float")) *
255).astype("uint8") # *fore.astype("float")
P = brightness(img) # get scaled image brightness
thresh, sure_bg = cv2.threshold(
P, 0, 1, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # obtain over threshold
thresh, sure_fg = cv2.threshold(P, thresh + 10, 1, cv2.THRESH_BINARY)
markers = np.ones_like(sure_fg).astype("int32") # make background markers
markers[sure_bg == 1] = 0 # mark unknown markers
markers[sure_fg == 1] = 2 # mark sure object markers
cv2.watershed(img, markers) # get watershed on markers
thresh, mask = cv2.threshold(markers.astype(
"uint8"), 1, 1, cv2.THRESH_BINARY) # get binary image of contour
if addMarkers:
return mask, thresh
return mask