"""Automatic Calculation of Regions of Interest.
"""
import numpy as np
import cv2
from .conventions import X, Y
from .scan import Scan, ScanSet
from .roi import HROI, VROI
from ..utils import linearoverlap, separateSpans
# # Return ComboROI with background guesses
# def findBackground(ss, tile_dims=None, mindivisions=10):
# if tile_dims is None:
# shorterdim = np.argmin(ss.dims)
# d = ss.dims[shorterdim]/mindivisions
# tile_dims = (d,d)
#
# tilemeans = np.zeros(shape=(20,10,maximg+1))
# regs = [[0]*10 for i in range(20)]
# for i in range(20):
# for j in range(10):
# reg = RROI((int(boxdims[X]*i),int(boxdims[Y]*j)),(int(boxdims[X]*(i+1)),int(boxdims[Y]*(j+1))))
# regs[i][j] = reg
# for n,s in enumerate(ss.scans[:maximg+1]):
# boxmeans[i,j,n] = np.mean(s.getImg(roi=reg))
# regs = np.array(regs)
# boxstds = np.std(boxmeans,axis=2)
# bkgboxindices = np.where(boxstds<np.mean(boxstds)/10)
# bkgregs = regs[bkgboxindices[0], bkgboxindices[1]]
# return ComboROI(*bkgregs)
#
# def guessLowcut(s, background=None):
# ... # Guess locut that will eliminate background
[docs]def calcHROIs(
scan,
min_width,
group_buffer=0,
return_lines=False
):
"""
Static function to separate image into columns based on line segments found
in image.
This works by combining line segements that overlap in their horizontal
ranges by more than 50% of their length to form groups. These groups are
then trimmed so as to not overlap. This is meant to be used with _calcLines
to find the horizontal ranges of pixels in the scan image that correspond to
each crystal. However, if two crystals are lined up such that they produce
a single line segment in the image, this function will create one group
spanning both crystals.
Parameters
----------
scan : :obj:`.core.scan.Scan`
Scan to calculate horizontal regions of interest for.
group_buffer : :obj:`int`
Minimum number of pixels to separate groups by.
return_lines : :obj:`bool`
Whether or not to return the lines segments detected in the image.
Returns
-------
:obj:`list`
List of :obj:`core.roi.HROI`'s representing regions corresponding to
each crystal.
:obj:`np.ndarray`, optional
Array of line segments detected in image, encoded as [[x1,y1],[x2,y2]].
Returned if ``return_lines`` is set :obj:`True`.
"""
s = scan.mod(blur=3)
lines2d = calcHorizontalLineSegments(s, round(min_width))
flattened = [[l[0][X], l[1][X]] for l in lines2d] # Flatten to just x coords
midyvals = [(l[0][Y]+l[1][Y])/2 for l in lines2d] # Midpoint y values
lines = [{'flat':flattened[i], 'midy': midyvals[i]} \
for i in range(len(flattened))]
# Combine overlapping lines
checkcomplete = False
while not checkcomplete:
matchfound = False
for l1 in range(len(lines)):
for l2 in range(len(lines)):
if l1==l2: continue
flat1 = lines[l1]['flat']
flat2 = lines[l2]['flat']
overlap = linearoverlap(flat1, flat2)
if overlap[0]>0.5 or overlap[1]>0.5:
newflat = [min(flat1[0], flat2[0]), max(flat1[1],flat2[1])]
newmidy = (lines[l1]['midy']+lines[l2]['midy'])/2
newline = {'flat':newflat, 'midy':newmidy}
lines.pop(max(l1,l2))
lines.pop(min(l1,l2))
lines.append(newline)
matchfound = True
break
if matchfound:
break
if not matchfound:
checkcomplete = True
groups = [l['flat'] for l in lines]
# Eliminate overlap between groups
groups = separateSpans(groups, group_buffer)
groups = np.array(groups)
groups = groups[np.argsort(groups[:,0])]
hrois = [HROI(g[0],g[1]) for g in groups if g[1]-g[0]>min_width]
if return_lines:
return hrois, lines2d
else:
return hrois
[docs]def calcHorizontalLineSegments(scan, min_line_length, anglerange=None):
"""
Function that calculates line segments from scan.
Parameters
----------
scan : :obj:`.scan.Scan`
Scan to calculate lines from.
min_line_length : :obj:`float`
Minimum length line segments must be.
anglerange : :obj:`tuple`
Range of angles that line segments must be within. Horizontal is 0,
vertical is pi/2. Range encoded as (t1,t2).
Returns
-------
:obj:`numpy.ndarray`
Array of line segments encoded as [[x1,y1],[x2,y2]]
"""
anglerange = anglerange or (0, np.pi/6)
fld = cv2.ximgproc.createFastLineDetector(length_threshold=min_line_length,\
do_merge=True)
img = scan.getImg().astype(int)
img[np.where(img>0)] = 255
rawlines = fld.detect(img.astype(np.uint8))
if rawlines is None:
return []
else:
lines = np.array(rawlines)[:,0,:]
lines[:,[X,Y]] = lines[:,[Y,X]] # Flip coords to x,y
lines[:,[X+2,Y+2]] = lines[:,[Y+2,X+2]]
tan_angles = np.abs((lines[:,Y]-lines[:,Y+2])/(lines[:,X]-lines[:,X+2]))
filteredlines = np.delete(lines, np.where(np.logical_and(
tan_angles<np.tan(anglerange[0]), tan_angles>np.tan(anglerange[1]))),
axis=0) # Only take lines inside angle range
# Split into pairs of points
lines2d = np.array([[[l[X],l[Y]],[l[X+2],l[Y+2]]] for l in filteredlines])
# Sort each line by x values of points
lines2d = np.array([l[np.argsort(l[:,0])] for l in lines2d])
return lines2d
[docs]def calcVROIs(
scan,
minheight,
maxgap,
cutoff_frac=0.1,
buffer=0
):
"""Calculate vertical regions of interest (VROIs) for a scan.
Parameters
----------
scan : :obj:`.core.scan.Scan`
Scan to calculate VROIs for.
minheight : :obj:`float`
Minimum height of VROI.
maxgap : :obj:`int`
Maximum empty (no non-zero pixels) space allowed within a VROI.
cutoff_frac : :obj:`float`
Fraction of peak intensity to use as low-cut.
buffer : :obj:`int`
Extra space to add around VROI.
"""
img = scan.getImg().copy()
img[img>0]=1
vd = img.sum(0)
cvd = vd.copy()
cvd[cvd<(np.max(vd)*cutoff_frac)] = 0
vroistart = -1
lastsaw = None
vrois = []
for x in range(scan.dims[Y]):
if cvd[x]>0: # If in a stretch
lastsaw = x
if vroistart == -1: vroistart = x
elif vroistart != -1 and x-lastsaw > maxgap: # If at a zero and empty stretch is fraction of roi so far
if lastsaw-vroistart > minheight: # If the length of thr roi is more than the minimum, complete it
lo = vroistart-buffer if vroistart>buffer else 0
hi = lastsaw+buffer if (lastsaw+buffer)<scan.dims[Y] else scan.dims[Y]
vrois.append(VROI(lo, hi))
vroistart = -1
else: vroistart = -1
if vroistart!=-1 and lastsaw-vroistart > minheight:
vrois.append(vrois.append(VROI(vroistart, lastsaw)))
return vrois
# def calcVROIs(
# scan:Scan,
# approx_height:Number,
# min_separation:Number=None, #TODO
# tightness=0.75
# ) -> Tuple[VROI]:
# """
# Function that calculates vertical regions of interest (VROIs) for each scan
# in a scan set. Each VROI is encoded as the y-pixel number of the center of
# the region and the total width of the region in vertical pixels.
#
# Args:
# scanset: set of scans to calculate VROIs for
#
# Returns:
# VROIs: Dictionary keyed by scan energy of VROIs encoded as y pixel
# values in tuple (center, width)
# numregions: Dictionary keyed by scan energy of number of possible ROIs
# """
# img = scan.getImg().copy()
# img[img>0]=1
# print(np.max(img))
# vertdensity = img.sum(0)
# blurv = gaussian_filter(vertdensity, sigma=approx_height) # Assumes calbration range covers entire CCD
# peaks, _ = find_peaks(blurv, height=blurv.min()+(blurv.max()-blurv.min())/10) # peaks >10% max, measured from min
# rel_height = tightness #if len(peaks) == 1 else 1
# width_data = peak_widths(blurv, peaks, rel_height=rel_height)
# vrois = [VROI(p-w/2,p+w/2) for p,w in zip(peaks, width_data[0])]
# return (vrois) # Convert to tuple since shouldn't be modified later