def estimate_angle(raw, maxskew=2, skewsteps=8, perc=80, range=20, zoom=0.5, bignore=0.1): comment = "" rawF = read_image_gray(raw) # perform image normalization image = rawF - amin(rawF) if amax(image) == amin(image): print "# image is empty", fname return image /= amax(image) extreme = (sum(image < 0.05) + sum(image > 0.95)) * 1.0 / prod(image.shape) if extreme > 0.95: comment += " no-normalization" flat = image else: # check whether the image is already effectively binarized # if not, we need to flatten it by estimating the local whitelevel m = interpolation.zoom(image, zoom) m = filters.percentile_filter(m, perc, size=(range, 2)) m = filters.percentile_filter(m, perc, size=(2, range)) m = interpolation.zoom(m, 1.0 / zoom) w, h = minimum(array(image.shape), array(m.shape)) flat = clip(image[:w, :h] - m[:w, :h] + 1, 0, 1) # estimate skew angle and rotate d0, d1 = flat.shape o0, o1 = int(bignore * d0), int(bignore * d1) flat = amax(flat) - flat flat -= amin(flat) est = flat[o0 : d0 - o0, o1 : d1 - o1] ma = maxskew ms = int(2 * maxskew * skewsteps) angle = estimate_skew_angle(est, linspace(-ma, ma, ms + 1)) return angle
def nlbin(im, threshold=0.5, zoom=0.5, escale=1.0, border=0.1, perc=80, range=20, low=5, high=90): """ Performs binarization using non-linear processing. Args: im (PIL.Image): threshold (float): zoom (float): Zoom for background page estimation escale (float): Scale for estimating a mask over the text region border (float): Ignore this much of the border perc (int): Percentage for filters range (int): Range for filters low (int): Percentile for black estimation high (int): Percentile for white estimation Returns: PIL.Image containing the binarized image """ if im.mode == '1': return im raw = pil2array(im) # rescale image to between -1 or 0 and 1 raw = raw/np.float(np.iinfo(raw.dtype).max) if raw.ndim == 3: raw = np.mean(raw, 2) # perform image normalization if np.amax(raw) == np.amin(raw): raise KrakenInputException('Image is empty') image = raw-np.amin(raw) image /= np.amax(image) m = interpolation.zoom(image, zoom) m = filters.percentile_filter(m, perc, size=(range, 2)) m = filters.percentile_filter(m, perc, size=(2, range)) m = interpolation.zoom(m, 1.0/zoom) w, h = np.minimum(np.array(image.shape), np.array(m.shape)) flat = np.clip(image[:w, :h]-m[:w, :h]+1, 0, 1) # estimate low and high thresholds d0, d1 = flat.shape o0, o1 = int(border*d0), int(border*d1) est = flat[o0:d0-o0, o1:d1-o1] # by default, we use only regions that contain # significant variance; this makes the percentile # based low and high estimates more reliable v = est-filters.gaussian_filter(est, escale*20.0) v = filters.gaussian_filter(v**2, escale*20.0)**0.5 v = (v > 0.3*np.amax(v)) v = morphology.binary_dilation(v, structure=np.ones((escale*50, 1))) v = morphology.binary_dilation(v, structure=np.ones((1, escale*50))) est = est[v] lo = np.percentile(est.ravel(), low) hi = np.percentile(est.ravel(), high) flat -= lo flat /= (hi-lo) flat = np.clip(flat, 0, 1) bin = np.array(255*(flat > threshold), 'B') return array2pil(bin)
def cleanMe(spec, window=75, p=10): # returns a cleaned spectrum, removing extreme outlier peaks and troughs a = np.array(spec) topcut = percentile_filter( a, 100-p, size=window ) a[ a>topcut ] = topcut[ a>topcut ] botcut = percentile_filter( a, p, size=window ) a[ a<botcut ] = botcut[ a<botcut ] return a
def order_statistics_filter(grid,window_size=(3,3,3),statistics_type='median',rank=1): filtered = grid.copy() if statistics_type=='minimum': scifilt.minimum_filter(grid,window_size,None,filtered, mode='nearest') elif statistics_type=='maximum': scifilt.maximum_filter(grid,window_size,None,filtered, mode='nearest') elif statistics_type=='median': scifilt.median_filter(grid,window_size,None,filtered, mode='nearest') elif statistics_type[:-2]=='percentile' or statistics_type[:-2]=='per': per = np.int(statistics_type[-2:]) scifilt.percentile_filter(grid,per,window_size,None,filtered, mode='nearest') elif statistics_type=='rank': scifilt.rank_filter(grid,rank,window_size,None,filtered, mode='nearest') return filtered return filtered
def normalize(self, method='percentile', window=None, perc=20, offset=0.1): """ Normalize by subtracting and dividing by a baseline. Baseline can be derived from a global mean or percentile, or a smoothed percentile estimated within a rolling window. Windowed baselines may only be well-defined for temporal series data. Parameters ---------- baseline : str, optional, default = 'percentile' Quantity to use as the baseline, options are 'mean', 'percentile', 'window', or 'window-exact'. window : int, optional, default = 6 Size of window for baseline estimation, for 'window' and 'window-exact' baseline only. perc : int, optional, default = 20 Percentile value to use, for 'percentile', 'window', or 'window-exact' baseline only. offset : float, optional, default = 0.1 Scalar added to baseline during division to avoid division by 0. """ check_options(method, ['mean', 'percentile', 'window', 'window-exact']) from warnings import warn if not (method == 'window' or method == 'window-exact') and window is not None: warn('Setting window without using method "window" has no effect') if method == 'mean': baseFunc = mean if method == 'percentile': baseFunc = lambda x: percentile(x, perc) if method == 'window': from scipy.ndimage.filters import percentile_filter baseFunc = lambda x: percentile_filter(x.astype(float64), perc, window, mode='nearest') if method == 'window-exact': if window & 0x1: left, right = (ceil(window/2), ceil(window/2) + 1) else: left, right = (window/2, window/2) n = len(self.index) baseFunc = lambda x: asarray([percentile(x[max(ix-left, 0):min(ix+right+1, n)], perc) for ix in arange(0, n)]) def get(y): b = baseFunc(y) return (y - b) / (b + offset) return self.map(get)
def normalize(self, baseline='percentile', window=None, perc=20): """ Normalize each time series by subtracting and dividing by a baseline. Baseline can be derived from a global mean or percentile, or a smoothed percentile estimated within a rolling window. Parameters ---------- baseline : str, optional, default = 'percentile' Quantity to use as the baseline, options are 'mean', 'percentile', 'window', or 'window-fast' window : int, optional, default = 6 Size of window for baseline estimation, for 'window' and 'window-fast' baseline only perc : int, optional, default = 20 Percentile value to use, for 'percentile', 'window', or 'window-fast' baseline only """ checkParams(baseline, ['mean', 'percentile', 'window', 'window-fast']) method = baseline.lower() from warnings import warn if not (method == 'window' or method == 'window-fast') and window is not None: warn('Setting window without using method "window" has no effect') if method == 'mean': baseFunc = mean if method == 'percentile': baseFunc = lambda x: percentile(x, perc) if method == 'window': if window & 0x1: left, right = (ceil(window/2), ceil(window/2) + 1) else: left, right = (window/2, window/2) n = len(self.index) baseFunc = lambda x: asarray([percentile(x[max(ix-left, 0):min(ix+right+1, n)], perc) for ix in arange(0, n)]) if method == 'window-fast': from scipy.ndimage.filters import percentile_filter baseFunc = lambda x: percentile_filter(x.astype(float64), perc, window, mode='nearest') def get(y): b = baseFunc(y) return (y - b) / (b + 0.1) return self.applyValues(get, keepIndex=True)
def deadPixelMask(self, pic): ''' pixels with much lower intensity compare to adjacent pixels will be masked :param pic: 2d array, image array to be processed :return: 2d array of boolean, 1 stands for masked pixel ''' avgpic = np.average(pic) ks = np.ones((5, 5)) ks1 = np.ones((7, 7)) picb = snf.percentile_filter(pic, 5, 3) < avgpic / 10 picb = snm.binary_dilation(picb, structure=ks) picb = snm.binary_erosion(picb, structure=ks1) return picb
def darkPixelMask(self, pic, r=None): ''' pixels with much lower intensity compare to adjacent pixels will be masked :param pic: 2d array, image array to be processed :param r: float, a threshold for masked pixels :return: 2d array of boolean, 1 stands for masked pixel ''' r = self.config.darkpixelr if r == None else r # 0.1 avgpic = np.average(pic) ks = np.ones((5, 5)) ks1 = np.ones((7, 7)) picb = snf.percentile_filter(pic, 5, 3) < avgpic * r picb = snm.binary_dilation(picb, structure=ks) picb = snm.binary_erosion(picb, structure=ks1) return picb
def ndi_med(image, n): return percentile_filter(image, 50, size=n * 2 - 1)
plt.xticks(range(0, 1500, 300), [0, '', '', 30, '']) plt.xlim(0, 1200) plt.ylim(0, 1.) plt.xlabel('Time [s]', labelpad=-10) plt.ylabel('Activity') # real data try: data = loadmat(filename) fmean_roi = data['obj']['timeSeriesArrayHash'].item()[0]['value'].item()[0][ 0]['valueMatrix'].item().ravel() fmean_neuropil = data['obj']['timeSeriesArrayHash'].item()[0]['value'].item()[0][ 1]['valueMatrix'].item().ravel() fmean_comp = np.ravel(fmean_roi - 0.7 * fmean_neuropil) b = percentile_filter(fmean_comp, 20, 3000, mode='nearest') mu = .075 # shift such that baseline is not negative, but ~0 y = ((fmean_comp - b) / (b + 10)).astype(float) + mu t_frame = data['obj']['timeSeriesArrayHash'].item()[0]['value'].item()[0][ 0]['time'].item().ravel() filt = data['obj']['timeSeriesArrayHash'].item()[0]['value'].item()[0][3][ 'valueMatrix'].item().ravel() t_ephys = data['obj']['timeSeriesArrayHash'].item()[0]['value'].item()[0][ 3]['time'].item().ravel() detected_spikes = data['obj']['timeSeriesArrayHash'].item()[0]['value'].item()[0][4][ 'valueMatrix'].item().ravel() spike_time = t_ephys[detected_spikes.astype(bool)]
def nlbin(im: Image, threshold: float = 0.5, zoom: float = 0.5, escale: float = 1.0, border: float = 0.1, perc: int = 80, range: int = 20, low: int = 5, high: int = 90) -> Image: """ Performs binarization using non-linear processing. Args: im (PIL.Image): threshold (float): zoom (float): Zoom for background page estimation escale (float): Scale for estimating a mask over the text region border (float): Ignore this much of the border perc (int): Percentage for filters range (int): Range for filters low (int): Percentile for black estimation high (int): Percentile for white estimation Returns: PIL.Image containing the binarized image Raises: KrakenInputException when trying to binarize an empty image. """ im_str = get_im_str(im) logger.info('Binarizing {}'.format(im_str)) if is_bitonal(im): logger.info( 'Skipping binarization because {} is bitonal.'.format(im_str)) return im # convert to grayscale first logger.debug('Converting {} to grayscale'.format(im_str)) im = im.convert('L') raw = pil2array(im) logger.debug('Scaling and normalizing') # rescale image to between -1 or 0 and 1 raw = raw / np.float(np.iinfo(raw.dtype).max) # perform image normalization if np.amax(raw) == np.amin(raw): logger.warning('Trying to binarize empty image {}'.format(im_str)) raise KrakenInputException('Image is empty') image = raw - np.amin(raw) image /= np.amax(image) logger.debug('Interpolation and percentile filtering') with warnings.catch_warnings(): warnings.simplefilter('ignore', UserWarning) m = interpolation.zoom(image, zoom) m = filters.percentile_filter(m, perc, size=(range, 2)) m = filters.percentile_filter(m, perc, size=(2, range)) mh, mw = m.shape oh, ow = image.shape scale = np.diag([mh * 1.0 / oh, mw * 1.0 / ow]) m = affine_transform(m, scale, output_shape=image.shape) w, h = np.minimum(np.array(image.shape), np.array(m.shape)) flat = np.clip(image[:w, :h] - m[:w, :h] + 1, 0, 1) # estimate low and high thresholds d0, d1 = flat.shape o0, o1 = int(border * d0), int(border * d1) est = flat[o0:d0 - o0, o1:d1 - o1] logger.debug('Threshold estimates {}'.format(est)) # by default, we use only regions that contain # significant variance; this makes the percentile # based low and high estimates more reliable logger.debug('Refine estimates') v = est - filters.gaussian_filter(est, escale * 20.0) v = filters.gaussian_filter(v**2, escale * 20.0)**0.5 v = (v > 0.3 * np.amax(v)) v = morphology.binary_dilation(v, structure=np.ones((int(escale * 50), 1))) v = morphology.binary_dilation(v, structure=np.ones((1, int(escale * 50)))) est = est[v] lo = np.percentile(est.ravel(), low) hi = np.percentile(est.ravel(), high) flat -= lo flat /= (hi - lo) flat = np.clip(flat, 0, 1) logger.debug('Thresholding at {}'.format(threshold)) bin = np.array(255 * (flat > threshold), 'B') return array2pil(bin)
def moving_window_atribute(data,atribute='mean',window=(3,3,3),percentile=50,rank=1,clip_limits=(0,1),fisher=True,border_mode='nearest'): #reflect’, ‘constant’, ‘nearest’, ‘mirror’, ‘wrap’ # minimum,maximum,median,percentile,rank,mean,variance,std,clip,sum,product,peak2peak,signal2noise,skewness # kurtosis if atribute == 'minimum': atrib = data.copy() scifilt.minimum_filter(data,window,None,atrib, mode=border_mode) return atrib elif atribute == 'maximum': atrib = data.copy() scifilt.maximum_filter(data,window,None,atrib, mode=border_mode) return atrib elif atribute == 'median': atrib = data.copy() scifilt.median_filter(data,window,None,atrib, mode=border_mode) return atrib elif atribute == 'percentile': atrib = data.copy() scifilt.percentile_filter(data,percentile,window,None,atrib, mode=border_mode) return atrib elif atribute == 'rank': atrib = data.copy() scifilt.rank_filter(data,rank,window,None,atrib, mode=border_mode) return atrib elif atribute == 'mean': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): atrib[i,j,k] = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].mean() return atrib elif atribute == 'variance': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): atrib[i,j,k] = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].var() return atrib elif atribute == 'std': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): atrib[i,j,k] = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].std() return atrib elif atribute == 'clip': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): m = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].flatten() l0 = np.percentile(m,clip_limits[0]) l1 = np.percentile(m,clip_limits[1]) m.clip(l0,l1) atrib[i,j,k] = np.percentile(m,50) return atrib elif atribute == 'sum': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): atrib[i,j,k] = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].sum() return atrib elif atribute == 'product': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): atrib[i,j,k] = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].prod() return atrib elif atribute == 'peak2peak': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): atrib[i,j,k] = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].ptp() return atrib elif atribute == 'signal2noise': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): m = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].mean() v = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].std() atrib[i,j,k] = m/v return atrib elif atribute == 'skewness': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): m = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].flatten() v = st.skew(m) atrib[i,j,k] = v return atrib elif atribute == 'kurtosis': atrib = data.copy() blocks = atrib.shape for i in xrange(blocks[0]): for j in xrange(blocks[1]): for k in xrange(blocks[2]): m = data[np.clip(i-window[0],0,blocks[0]):i+window[0]+1,np.clip(j-window[1],0,blocks[1]):j+window[1]+1,np.clip(k-window[2],0,blocks[2]):k+window[2]+1].flatten() v = st.kurtosis(m,fisher=fisher) atrib[i,j,k] = v return atrib else: return False
def low_high(d, winsize): high = percentile_filter(d, 95, winsize) low = percentile_filter(d, 5, winsize) high = filters.lowpass_fir(high, winsize) low = filters.lowpass_fir(low, winsize) return low, high
def _process_segment(self, page_image, page, page_xywh, page_id, input_file, n): LOG = getLogger('OcrdAnybaseocrBinarizer') raw = ocrolib.pil2array(page_image) if len(raw.shape) > 2: raw = np.mean(raw, 2) raw = raw.astype("float64") # perform image normalization image = raw - amin(raw) if amax(image) == amin(image): LOG.info("# image is empty: %s" % (page_id)) return image /= amax(image) # check whether the image is already effectively binarized if self.parameter['gray']: extreme = 0 else: extreme = (np.sum(image < 0.05) + np.sum(image > 0.95)) * 1.0 / np.prod(image.shape) if extreme > 0.95: comment = "no-normalization" flat = image else: comment = "" # if not, we need to flatten it by estimating the local whitelevel LOG.info("Flattening") m = interpolation.zoom(image, self.parameter['zoom']) m = filters.percentile_filter(m, self.parameter['perc'], size=(self.parameter['range'], 2)) m = filters.percentile_filter(m, self.parameter['perc'], size=(2, self.parameter['range'])) m = interpolation.zoom(m, 1.0 / self.parameter['zoom']) if self.parameter['debug'] > 0: clf() imshow(m, vmin=0, vmax=1) ginput(1, self.parameter['debug']) w, h = minimum(array(image.shape), array(m.shape)) flat = clip(image[:w, :h] - m[:w, :h] + 1, 0, 1) if self.parameter['debug'] > 0: clf() imshow(flat, vmin=0, vmax=1) ginput(1, self.parameter['debug']) # estimate low and high thresholds LOG.info("Estimating Thresholds") d0, d1 = flat.shape o0, o1 = int(self.parameter['bignore'] * d0), int( self.parameter['bignore'] * d1) est = flat[o0:d0 - o0, o1:d1 - o1] if self.parameter['escale'] > 0: # by default, we use only regions that contain # significant variance; this makes the percentile # based low and high estimates more reliable e = self.parameter['escale'] v = est - filters.gaussian_filter(est, e * 20.0) v = filters.gaussian_filter(v**2, e * 20.0)**0.5 v = (v > 0.3 * amax(v)) v = morphology.binary_dilation(v, structure=ones((int(e * 50), 1))) v = morphology.binary_dilation(v, structure=ones((1, int(e * 50)))) if self.parameter['debug'] > 0: imshow(v) ginput(1, self.parameter['debug']) est = est[v] lo = stats.scoreatpercentile(est.ravel(), self.parameter['lo']) hi = stats.scoreatpercentile(est.ravel(), self.parameter['hi']) # rescale the image to get the gray scale image LOG.info("Rescaling") flat -= lo flat /= (hi - lo) flat = clip(flat, 0, 1) if self.parameter['debug'] > 0: imshow(flat, vmin=0, vmax=1) ginput(1, self.parameter['debug']) binarized = 1 * (flat > self.parameter['threshold']) # output the normalized grayscale and the thresholded images # print_info("%s lo-hi (%.2f %.2f) angle %4.1f %s" % (fname, lo, hi, angle, comment)) LOG.info("%s lo-hi (%.2f %.2f) %s" % (page_id, lo, hi, comment)) LOG.info("writing") if self.parameter['debug'] > 0 or self.parameter['show']: clf() gray() imshow(binarized) ginput(1, max(0.1, self.parameter['debug'])) page_xywh['features'] += ',binarized' bin_array = array(255 * (binarized > ocrolib.midrange(binarized)), 'B') bin_image = ocrolib.array2pil(bin_array) file_id = make_file_id(input_file, self.output_file_grp) file_path = self.workspace.save_image_file( bin_image, file_id + '-IMG', page_id=page_id, file_grp=self.output_file_grp) page.add_AlternativeImage( AlternativeImageType(filename=file_path, comments=page_xywh['features']))
def dbl_pct_filt(arr): # Define percentile filter for clipping off artefactual IAS protrusions due to dangling epidermis out = percentile_filter(percentile_filter(arr, size=30, percentile=10), size=30, percentile=90) return out
def nlbin(im: Image.Image, threshold: float = 0.5, zoom: float = 0.5, escale: float = 1.0, border: float = 0.1, perc: int = 80, range: int = 20, low: int = 5, high: int = 90) -> Image: """ Performs binarization using non-linear processing. Args: im (PIL.Image.Image): threshold (float): zoom (float): Zoom for background page estimation escale (float): Scale for estimating a mask over the text region border (float): Ignore this much of the border perc (int): Percentage for filters range (int): Range for filters low (int): Percentile for black estimation high (int): Percentile for white estimation Returns: PIL.Image containing the binarized image Raises: KrakenInputException when trying to binarize an empty image. """ im_str = get_im_str(im) logger.info('Binarizing {}'.format(im_str)) if is_bitonal(im): logger.info('Skipping binarization because {} is bitonal.'.format(im_str)) return im # convert to grayscale first logger.debug('Converting {} to grayscale'.format(im_str)) im = im.convert('L') raw = pil2array(im) logger.debug('Scaling and normalizing') # rescale image to between -1 or 0 and 1 raw = raw/np.float(np.iinfo(raw.dtype).max) # perform image normalization if np.amax(raw) == np.amin(raw): logger.warning('Trying to binarize empty image {}'.format(im_str)) raise KrakenInputException('Image is empty') image = raw-np.amin(raw) image /= np.amax(image) logger.debug('Interpolation and percentile filtering') with warnings.catch_warnings(): warnings.simplefilter('ignore', UserWarning) m = interpolation.zoom(image, zoom) m = filters.percentile_filter(m, perc, size=(range, 2)) m = filters.percentile_filter(m, perc, size=(2, range)) mh, mw = m.shape oh, ow = image.shape scale = np.diag([mh * 1.0/oh, mw * 1.0/ow]) m = affine_transform(m, scale, output_shape=image.shape) w, h = np.minimum(np.array(image.shape), np.array(m.shape)) flat = np.clip(image[:w, :h]-m[:w, :h]+1, 0, 1) # estimate low and high thresholds d0, d1 = flat.shape o0, o1 = int(border*d0), int(border*d1) est = flat[o0:d0-o0, o1:d1-o1] logger.debug('Threshold estimates {}'.format(est)) # by default, we use only regions that contain # significant variance; this makes the percentile # based low and high estimates more reliable logger.debug('Refine estimates') v = est-filters.gaussian_filter(est, escale*20.0) v = filters.gaussian_filter(v**2, escale*20.0)**0.5 v = (v > 0.3*np.amax(v)) v = morphology.binary_dilation(v, structure=np.ones((int(escale * 50), 1))) v = morphology.binary_dilation(v, structure=np.ones((1, int(escale * 50)))) est = est[v] lo = np.percentile(est.ravel(), low) hi = np.percentile(est.ravel(), high) flat -= lo flat /= (hi-lo) flat = np.clip(flat, 0, 1) logger.debug('Thresholding at {}'.format(threshold)) bin = np.array(255*(flat > threshold), 'B') return array2pil(bin)