Source code for lfd.detecttrails.processfield

"""Processfield is the image analysis workhorse module behind detecttrails. It
contains all the functionality required to detect lines independend of the
source of data, only the ingoing format and type.

Wrapping the functionality in this module for various different catalog and
image sources so that the correct order of operations is ensured for different
directory structures is what makes detecttrails capable of processing variety
of different images.
"""

import cv2
import numpy as np
import os as os

__all__ = ["process_field_bright", "process_field_dim", "pathBright"]

pathBright = None
pathDim = None

[docs]def setup_debug(): """Sets up the module global variables - paths to where the debug output is saved. Invoked when 'debug' key is set to True for any of the detection steps. Generally there would be no need to call this function otherwise. """ try: global pathBright global pathDim pathBright = os.environ["DEBUG_PATH"] pathDim = os.environ["DEBUG_PATH"] except: pass
[docs]def check_theta(hough1, hough2, navg, dro, thetaTresh, lineSetTresh, debug): """ Comapres colinearity between lines in each set of lines provided and between the two sets. If the lines in each set are nearly parallel and the two sets are nearly parallel and if the lines are intersecting the x axis at nearly the same point then they are nearly colinear. .. warning:: Function returns True when a test is satisfied - this means that the lines are not colinear. Calculates the difference between max and min angle values of each set of fitted lines. If these diffs. are larger than thetaTresh, returns True. .. code-block:: python dtheta = abs(theta.max()-theta.min()) if dtheta2> theta_tresh: return True Difference of averages of angles in both sets are compared with linesetTresh. If the diff. is larger than linesetTresh a True is returned. .. code-block:: python dtheta = abs(numpy.average(theta1-theta2)) if numpy.average(dtheta)> lineset_tresh: return True If the average x axis intersection of the two line sets are not close enough True is returned .. code-block:: python if abs(np.average(ro1)-np.average(ro2))>dro: return True Parameters ---------- hough1 : cv2.HoughLines 2D array of line parameters representing the first set of lines that will be compared. Line parameters are stored as tuples, i.e. hough1[0][i] --> tuple(ro, theta) hough2 : cv2.HoughLines second set of lines to be compared navg : int number of lines that will be averaged together thetaTresh : float treshold, in radians, that each line set must not be bigger than linesetTresh : float treshold, in radians, that the difference of the two line sets should not be bigger than. debug : bool produces a verboose output of calculated values. """ ro1 = np.zeros((navg,1)) ro2 = np.zeros((navg,1)) theta1 = np.zeros((navg,1)) theta2 = np.zeros((navg,1)) for i in range(0, navg): try: # changed with opencv 3, the shape is (N, 1, 2) where N is number # of detected lines, 1 is just cause, and 2 for (ro, theta pairs) ro1[i] = hough1[i][0][0] ro2[i] = hough2[i][0][0] theta1[i] = hough1[i][0][1] theta2[i] = hough2[i][0][1] except: pass if debug: print ("RO: " ) print ("Ro_tresh: ", dro ) print (" PROCESED IMAGE: " ) print (" ro1: ", ro1.tolist() ) print (" avg(ro1): ", np.average(ro1) ) print (" MINAREARECT IMAGE: " ) print (" ro2: ", ro2.tolist() ) print (" avg(ro2): ", np.average(ro2) ) print ("------------------------------------------" ) print ("avg1-avg2: ", abs(np.average(ro1)-np.average(ro2)) ) if abs(np.average(ro1)-np.average(ro2))>dro: if debug: print("Ro test: FAILED") return True if debug: print ("\nTHETA: " ) print ("Theta_tresh: ", thetaTresh ) print ("Lineset_tresh ", lineSetTresh ) print (" PROCESED IMAGE: " ) print (" theta1 ", theta1.tolist() ) print (" max1-min1 ", abs(theta1.max()-theta1.min()) ) print (" MINAREARECT IMAGE: " ) print (" theta2 ", theta2.tolist() ) print (" max2-min2 ", abs(theta2.max()-theta2.min()) ) print ("------------------------------------------" ) print ("Average(theta1-theta2): ",abs(np.average(theta1-theta2))) dtheta1=abs(theta1.max()-theta1.min()) if dtheta1> thetaTresh: if debug: print("Theta1 tresh test: FAILED") return True dtheta2=abs(theta2.max()-theta2.min()) if dtheta2> thetaTresh: if debug: print ("Theta2 tresh test: FAILED") return True dtheta = abs(theta1-theta2) if np.average(dtheta)> lineSetTresh: if debug: print ("Lineset tresh test: FAILED") return True
[docs]def draw_lines(hough, image, nlines, name, path=pathDim, compression=0, color=(255,0,0)): """ Draws hough lines on a given image and saves it as a png. Parameters ---------- hough : cv2.HoughLines 2D array of line parameters, line parameters are stored in [0][x] as tuples. image : np.array or cv2.image image will now be altered nlines : int number of lines to draw name : str name of the file without extension path : str the path will be set by default when DetectTrails debug params are set to True. Otherwise supply your own. compression : int cv2.IMWRITE_PNG_COMPRESSION parameter from 0 to 9. A higher value means a smaller size and longer compression time. Default value is 0. """ n_x, n_y=image.shape #convert to color image so that you can see the lines draw_im = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) for houghparams in hough[:nlines]: try: rho, theta = houghparams[0] x0 = np.cos(theta)*rho y0 = np.sin(theta)*rho pt1 = ( int(x0 + (n_x+n_y)*(-np.sin(theta))), int(y0 + (n_x+n_y)*np.cos(theta)) ) pt2 = ( int(x0 - (n_x+n_y)*(-np.sin(theta))), int(y0 - (n_x+n_y)*np.cos(theta)) ) cv2.line(draw_im, pt1, pt2, color, 2) except: pass cv2.imwrite(os.path.join(path, name+".png"), draw_im, [cv2.IMWRITE_PNG_COMPRESSION, compression])
[docs]def fit_minAreaRect(img, contoursMode, contoursMethod, minAreaRectMinLen, lwTresh, debug): """ Fits minimal area rectangles to the image. If no rectangles can be fitted it returns False. Otherwise returns True and an image with drawn rectangles. 1. finds edges using canny edge detection algorithm 2. finds all contours among the edges 3. fits a min. area rectangle to a contour only if: a. both sides of the rect. are longer than minAreaRectMinLen b. the ratio of longer vs, shorter rect. side is smaller than lwTresh 5. if no rectangles satisfying sent conditions are found function returns False and an empty (black) image For more details on the parameters see documentation. Parameters ---------- img : np.array numpy array representing gray 8 bit 1 chanel image. lwTresh : float ratio of longer/shorter rectangle side that needs to be satisfied contoursMode : cv2.const cv2.RETR_LIST should be used, but any of the contour return modes contoursMethod : cv2.const cv2.CHAIN_APPROX_NONE should be used, but any of the contour return modes supported by OpenCV can be used minAreaRectMinLen : int length, in pixels, of allowed shortest side to be fitted to contours """ detection = False box_img = np.zeros(img.shape, dtype=np.uint8) canny = cv2.Canny(img, 0, 255) # in in cv2.8something it was contours, hierarchy # then in 3 it was image, contours, hierarchy, and # then in 4.0 it's again contours, hierarchy.... try: contoursimg, contours, hierarchy = cv2.findContours(canny, contoursMode, contoursMethod) except ValueError: contours, hierarchy = cv2.findContours(canny, contoursMode, contoursMethod) boxes = list() for cnt in contours: rect = cv2.minAreaRect(cnt) if (rect[1][0]>rect[1][1]): l = rect[1][0] w = rect[1][1] else: w = rect[1][0] l = rect[1][1] if l>minAreaRectMinLen and w>minAreaRectMinLen: if (l/w>lwTresh): detection = True box = cv2.boxPoints(rect) box = np.asarray(box, dtype=np.int32) cv2.fillPoly(box_img, [box], (255, 255,255)) return detection, box_img
[docs]def dictify_hough(shape, houghVals): """Function converts from hough line tuples (rho, theta) into a dictionary of pixel coordinates on the image. Parameters ---------- shape : tuple, list shape (dimensions) of the image. Used in order to scale Hough-space coordinates into pixel-space coordinates houghVals : tuple, list (rho, theta) values of lines """ rho, theta = houghVals n_x, n_y = shape x0 = np.cos(theta)*rho y0 = np.sin(theta)*rho x1 = int(x0 - (n_x+n_y)*np.sin(theta)) y1 = int(y0 + (n_x+n_y)*np.cos(theta)) x2 = int(x0 + (n_x+n_y)*np.sin(theta)) y2 = int(y0 - (n_x+n_y)*np.cos(theta)) return {"x1":x1, "y1":y1, "x2":x2, "y2":y2}
[docs]def process_field_bright(img, lwTresh, thetaTresh, dilateKernel, contoursMode, contoursMethod, minAreaRectMinLen, houghMethod, nlinesInSet, lineSetTresh, dro, debug): """ Function detects bright trails in images. For parameters explanations see DetectTrails documentation for help. 1. pixels with values bellow minFlux are set to 0 2. Scale the image to 8 bit integer, 1 channel 3. histogram equalization 4. dilate to expand features 5. fit minimal area rectangles. Function aborts if no minAreaRect are found, see: fit_minAreaRect help 6. fit Hough lines on image and on found rectangles 7. compare lines, see: check_theta help 8. if evaluation passed write the results into file and return True, else returns False. Parameters ---------- img : np.array numpy array representing gray 32 bit 1 chanel image. lwTresh : float treshold for the ratio of rectangle side lengths that has to be satistfied thetatresh : float treshold for the difference of angles, in radians, that each fitted set of lines has to satisfy dilateKernel : np.array kernel used to dilate the image contoursMode : cv2.const cv2.RETR_LIST should be used, but any return type of fitted contours supported by OpenCV is allowed contoursMethod : cv2.const cv2.CHAIN_APPROX_NONE should be used, but any return type of fitted contours supported by OpenCV is allowed minAreaRectMinLen : int treshold for minimal allowed length of fitted minimal area rectangles houghMethod : int treshold for minimum number of votes lines need to get in Hough space to be returned nlinesInSet : int number of most voted for lines that will be considered for colinearity tests lineSetTresh : float treshold for maximal allowed angle deviation between two sets of fitted lines dro : int treshold for maximal allowed distance between the average x-axis intersection coordinates of the two sets of lines """ img[img<0] = 0 #FITS files are usually 1 channel 32 bit float images, we need # 1 channel 8 bit int images for OpenCV gray_image = cv2.convertScaleAbs(img) equ = cv2.equalizeHist(gray_image) if debug: cv2.imwrite(os.path.join(pathBright, "1equBRIGHT.png"), equ, [cv2.IMWRITE_PNG_COMPRESSION, 3]) print("BRIGHT: saving EQU with removed stars") equ = cv2.dilate(equ, dilateKernel) if debug: cv2.imwrite(os.path.join(pathBright, "2dilateBRIGHT.png"), equ, [cv2.IMWRITE_PNG_COMPRESSION, 3]) print("BRIGHT: saving dilated image.") detection, box_img = fit_minAreaRect(equ, contoursMode, contoursMethod, minAreaRectMinLen, lwTresh, debug) if debug: cv2.imwrite(os.path.join(pathBright, "3contoursBRIGHT.png"), box_img, [cv2.IMWRITE_PNG_COMPRESSION, 3]) print ("BRIGHT: saving contours") if detection: equhough = cv2.HoughLines(equ, houghMethod, np.pi/180, 1) boxhough = cv2.HoughLines(box_img, houghMethod, np.pi/180, 1) if debug: draw_lines(equhough, equ, nlinesInSet, "5equhoughBRIGHT", path=pathBright) draw_lines(boxhough, box_img, nlinesInSet, "4boxhoughBRIGHT", path=pathBright) print("BRIGHT!") if check_theta(equhough, boxhough, nlinesInSet, dro, thetaTresh, lineSetTresh, debug): return (False, None) else: return (True, dictify_hough(equ.shape, equhough[0][0])) else: if debug: print("BRIGHT: no boxes found") return (False, None)
[docs]def process_field_dim(img, minFlux, addFlux, lwTresh, thetaTresh, erodeKernel, dilateKernel, contoursMode, contoursMethod, minAreaRectMinLen, houghMethod, nlinesInSet, dro, lineSetTresh, debug): """ Function detects dim trails in images. See DetectTrails documentation for more detailed explanation of parameters 1. pixels with values bellow minFlux are set to 0 2. addFlux is added to remaining pixels 3. Scale the image to 8 bit 1 chanel 4. histogram equalization 5. erode to kill noise 6. dilate to expand features that survived 7. fit minimal area rectangles. Function aborts if no minAreaRect are found, see: fit_minAreaRect help 8. fit Hough lines on image and on found rectangles 9. compare lines, see: help(check_theta) 10. if evaluation passed write the results into file and return True, else returns False. Parameters ---------- img : np.array numpy array representing gray 32 bit 1 chanel image. minFlux : float treshold for maximal allowed pixel brightness value under which pixel values will be set to zero addFlux : float the brightness that will be added to all pixels above minFlux lwTresh : float treshold for the ratio of rectangle side lengths that has to be satistfied thetatresh : float treshold for the difference of angles, in radians, that each fitted set of lines has to satisfy dilateKernel : np.array kernel used to erode the image dilateKernel : np.array kernel used to dilate the image contoursMode : cv2.const cv2.RETR_LIST should be used, but any return type of fitted contours supported by OpenCV is allowed contoursMethod : cv2.const cv2.CHAIN_APPROX_NONE should be used, but any return type of fitted contours supported by OpenCV is allowed minAreaRectMinLen : int treshold for minimal allowed length of fitted minimal area rectangles houghMethod : int treshold for minimum number of votes lines need to get in Hough space to be returned nlinesInSet : int number of most voted for lines that will be considered for colinearity tests lineSetTresh : float treshold for maximal allowed angle deviation between two sets of fitted lines dro : int treshold for maximal allowed distance between the average x-axis intersection coordinates of the two sets of lines """ img[img<minFlux]=0 img[img>0]+=addFlux gray_image = cv2.convertScaleAbs(img) equ = cv2.equalizeHist(gray_image) if debug: print("DIM: saving EQU with stars removed") cv2.imwrite(os.path.join(pathDim, "6equDIM.png"), equ, [cv2.IMWRITE_PNG_COMPRESSION, 0]) opening = cv2.erode(equ, erodeKernel) if debug: print("DIM: saving eroded EQU with stars removed") cv2.imwrite(os.path.join(pathDim, '7erodedDIM.png'), opening, [cv2.IMWRITE_PNG_COMPRESSION, 0]) equ = cv2.dilate(opening, dilateKernel) if debug: print("DIM: saving dilated eroded EQU with stars removed") cv2.imwrite(os.path.join(pathDim, '8openedDIM.png'), equ, [cv2.IMWRITE_PNG_COMPRESSION, 0]) detection, box_img = fit_minAreaRect(equ, contoursMode, contoursMethod, minAreaRectMinLen, lwTresh, debug) if debug: cv2.imwrite(os.path.join(pathDim, "9contoursDIM.png"), box_img, [cv2.IMWRITE_PNG_COMPRESSION, 0]) print("DIM: saving contours") if detection: equhough = cv2.HoughLines(equ, houghMethod, np.pi/180, 1) boxhough = cv2.HoughLines(box_img, houghMethod, np.pi/180, 1) if debug: draw_lines(equhough, equ, nlinesInSet, "10equhoughDIM", compression=4, path=pathDim) draw_lines(boxhough, box_img, nlinesInSet, "11boxhoughDIM", compression=4, color=(0,0,255), path=pathDim) print("DIM!") if check_theta(equhough, boxhough, nlinesInSet, dro, thetaTresh, lineSetTresh, debug): return (False, None) else: return (True, dictify_hough(equ.shape, equhough[0][0])) else: if debug: print("DIM: FALSE AT NO RECTANGLES MATCHING THE CONDITIONS FOUND!") return (False, None)