def getThresholdedStripe3DPoints(config, img_handle, frame_min, frame_max, rho, theta, mask, flat_struct, \ dark, stripe_width_factor=1.0, centroiding=False, point1=None, point2=None, debug=False): """ Threshold the image and get a list of pixel positions and frames of threshold passers. This function handles all input types of data. Arguments; config: [config object] configuration object (loaded from the .config file). img_handle: [FrameInterface instance] Object which has a common interface to various input files. frame_min: [int] First frame to process. frame_max: [int] Last frame to process. rho: [float] Line distance from the center in HT space (pixels). theta: [float] Angle in degrees in HT space. mask: [ndarray] Image mask. flat_struct: [Flat struct] Structure containing the flat field. None by default. dark: [ndarray] Dark frame. Keyword arguments: stripe_width_factor: [float] Multipler by which the default stripe width will be multiplied. Default is 1.0 centroiding: [bool] If True, the indices will be returned in the centroiding mode, which means that point1 and point2 arguments must be given. point1: [list] (x, y, frame) Of the first reference point of the detection. point2: [list] (x, y, frame) Of the second reference point of the detection. debug: [bool] If True, extra debug messages and plots will be shown. Return: xs, ys, zs: [tuple of lists] Indices of (x, y, frame) of threshold passers for every frame. """ # Get indices of stripe pixels around the line of the meteor img_h, img_w = img_handle.ff.maxpixel.shape stripe_indices = getStripeIndices( rho, theta, stripe_width_factor * config.stripe_width, img_h, img_w) # If centroiding should be done, prepare everything for cutting out parts of the image for photometry if centroiding: # Compute the unit vector which describes the motion of the meteor in the image domain point1 = np.array(point1) point2 = np.array(point2) motion_vect = point2[:2] - point1[:2] motion_vect_unit = vectNorm(motion_vect) # Get coordinates of 2 points that describe the line x1, y1, z1 = point1 x2, y2, z2 = point2 # Compute the average angular velocity in px per frame ang_vel = np.sqrt((x2 - x1)**2 + (y2 - y1)**2) / (z2 - z1) # Compute the vector describing the length and direction of the meteor per frame motion_vect = ang_vel * motion_vect_unit # If the FF files is given, extract the points from FF after threshold if img_handle.input_type == 'ff': # Threshold the FF file img_thres = Image.thresholdFF(img_handle.ff, config.k1_det, config.j1_det, mask=mask, \ mask_ave_bright=False) # Extract the thresholded image by min and max frames from FF file img = selectFFFrames(np.copy(img_thres), img_handle.ff, frame_min, frame_max) # Remove lonely pixels img = morph.clean(img) # Extract the stripe from the thresholded image stripe = np.zeros(img.shape, img.dtype) stripe[stripe_indices] = img[stripe_indices] # Show stripe # show2("stripe", stripe*255) # Show 3D could # show3DCloud(ff, stripe) # Get stripe positions (x, y, frame) stripe_positions = stripe.nonzero() xs = stripe_positions[1] ys = stripe_positions[0] zs = img_handle.ff.maxframe[stripe_positions] return xs, ys, zs # If video frames are available, extract indices on all frames in the given range else: xs_array = [] ys_array = [] zs_array = [] # Go through all frames in the frame range for fr in range(frame_min, frame_max + 1): # Break the loop if outside frame size if fr == (img_handle.total_frames - 1): break # Set the frame number img_handle.setFrame(fr) # Load the frame fr_img = img_handle.loadFrame() # Apply the dark frame if dark is not None: fr_img = Image.applyDark(fr_img, dark) # Apply the flat to frame if flat_struct is not None: fr_img = Image.applyFlat(fr_img, flat_struct) # Mask the image fr_img = MaskImage.applyMask(fr_img, mask) # Threshold the frame img_thres = Image.thresholdImg(fr_img, img_handle.ff.avepixel, img_handle.ff.stdpixel, \ config.k1_det, config.j1_det, mask=mask, mask_ave_bright=False) # Remove lonely pixels img_thres = morph.clean(img_thres) # Extract the stripe from the thresholded image stripe = np.zeros(img_thres.shape, img_thres.dtype) stripe[stripe_indices] = img_thres[stripe_indices] # Include more pixels for centroiding and photometry and mask out per frame pixels if centroiding: # Dilate the pixels in the stripe twice, to include more pixels for photometry stripe = morph.dilate(stripe) stripe = morph.dilate(stripe) # Get indices of the stripe that is perpendicular to the meteor, and whose thickness is the # length of the meteor on this particular frame - this is called stripe_indices_motion # Compute the previous, current, and the next linear model position of the meteor on the # image model_pos_prev = point1[:2] + (fr - 1 - z1) * motion_vect model_pos = point1[:2] + (fr - z1) * motion_vect model_pos_next = point1[:2] + (fr + 1 - z1) * motion_vect # Get the rho, theta of the line perpendicular to the meteor line x_inters, y_inters = model_pos # Check if the previous, current or the next centroids are outside bounds, and if so, skip the # frame if (not checkCentroidBounds(model_pos_prev, img_w, img_h)) or \ (not checkCentroidBounds(model_pos, img_w, img_h)) or \ (not checkCentroidBounds(model_pos_next, img_w, img_h)): continue # Get parameters of the perpendicular line to the meteor line rho2, theta2 = htLinePerpendicular(rho, theta, x_inters, y_inters, img_h, img_w) # Compute the image indices of this position which will be the intersection with the stripe # The width of the line will be 2x the angular velocity stripe_length = 6 * ang_vel if stripe_length < stripe_width_factor * config.stripe_width: stripe_length = stripe_width_factor * config.stripe_width stripe_indices_motion = getStripeIndices( rho2, theta2, stripe_length, img_h, img_w) # Mark only those parts which overlap both lines, which effectively creates a mask for # photometry an centroiding, excluding other influences stripe_new = np.zeros_like(stripe) stripe_new[stripe_indices_motion] = stripe[ stripe_indices_motion] stripe = stripe_new if debug: # Show the extracted stripe img_stripe = np.zeros_like(stripe) img_stripe[stripe_indices] = 1 final_stripe = np.zeros_like(stripe) final_stripe[stripe_indices_motion] = img_stripe[ stripe_indices_motion] plt.imshow(final_stripe) plt.show() if debug and centroiding: print(fr) print('mean stdpixel3:', np.mean(img_handle.ff.stdpixel)) print('mean avepixel3:', np.mean(img_handle.ff.avepixel)) print('mean frame:', np.mean(fr_img)) fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True, sharey=True) fr_img_noavg = Image.applyDark(fr_img, img_handle.ff.avepixel) #fr_img_noavg = fr_img # Auto levels min_lvl = np.percentile(fr_img_noavg[2:, :], 1) max_lvl = np.percentile(fr_img_noavg[2:, :], 99.0) # Adjust levels fr_img_autolevel = Image.adjustLevels(fr_img_noavg, min_lvl, 1.0, max_lvl) ax1.imshow(stripe, cmap='gray') ax2.imshow(fr_img_autolevel, cmap='gray') plt.show() pass # Get stripe positions (x, y, frame) stripe_positions = stripe.nonzero() xs = stripe_positions[1] ys = stripe_positions[0] zs = np.zeros_like(xs) + fr # Add the points to the list xs_array.append(xs) ys_array.append(ys) zs_array.append(zs) if debug: print('---') print(stripe.nonzero()) print(xs, ys, zs) if len(xs_array) > 0: # Flatten the arrays xs_array = np.concatenate(xs_array) ys_array = np.concatenate(ys_array) zs_array = np.concatenate(zs_array) else: xs_array = np.array(xs_array) ys_array = np.array(ys_array) zs_array = np.array(zs_array) return xs_array, ys_array, zs_array
def getThresholdedStripe3DPoints(config, img_handle, frame_min, frame_max, rho, theta, mask, flat_struct, \ dark, stripe_width_factor=1.0, centroiding=False, point1=None, point2=None, debug=False): """ Threshold the image and get a list of pixel positions and frames of threshold passers. This function handles all input types of data. Arguments; config: [config object] configuration object (loaded from the .config file). img_handle: [FrameInterface instance] Object which has a common interface to various input files. frame_min: [int] First frame to process. frame_max: [int] Last frame to process. rho: [float] Line distance from the center in HT space (pixels). theta: [float] Angle in degrees in HT space. mask: [ndarray] Image mask. flat_struct: [Flat struct] Structure containing the flat field. None by default. dark: [ndarray] Dark frame. Keyword arguments: stripe_width_factor: [float] Multipler by which the default stripe width will be multiplied. Default is 1.0 centroiding: [bool] If True, the indices will be returned in the centroiding mode, which means that point1 and point2 arguments must be given. point1: [list] (x, y, frame) Of the first reference point of the detection. point2: [list] (x, y, frame) Of the second reference point of the detection. debug: [bool] If True, extra debug messages and plots will be shown. Return: xs, ys, zs: [tuple of lists] Indices of (x, y, frame) of threshold passers for every frame. """ # Get indices of stripe pixels around the line of the meteor img_h, img_w = img_handle.ff.maxpixel.shape stripe_indices = getStripeIndices(rho, theta, stripe_width_factor*config.stripe_width, img_h, img_w) # If centroiding should be done, prepare everything for cutting out parts of the image for photometry if centroiding: # Compute the unit vector which describes the motion of the meteor in the image domain point1 = np.array(point1) point2 = np.array(point2) motion_vect = point2[:2] - point1[:2] motion_vect_unit = vectNorm(motion_vect) # Get coordinates of 2 points that describe the line x1, y1, z1 = point1 x2, y2, z2 = point2 # Compute the average angular velocity in px per frame ang_vel = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)/(z2 - z1) # Compute the vector describing the length and direction of the meteor per frame motion_vect = ang_vel*motion_vect_unit # If the FF files is given, extract the points from FF after threshold if img_handle.input_type == 'ff': # Threshold the FF file img_thres = Image.thresholdFF(img_handle.ff, config.k1_det, config.j1_det, mask=mask, \ mask_ave_bright=False) # Extract the thresholded image by min and max frames from FF file img = selectFFFrames(np.copy(img_thres), img_handle.ff, frame_min, frame_max) # Remove lonely pixels img = morph.clean(img) # Extract the stripe from the thresholded image stripe = np.zeros(img.shape, img.dtype) stripe[stripe_indices] = img[stripe_indices] # Show stripe # show2("stripe", stripe*255) # Show 3D could # show3DCloud(ff, stripe) # Get stripe positions (x, y, frame) stripe_positions = stripe.nonzero() xs = stripe_positions[1] ys = stripe_positions[0] zs = img_handle.ff.maxframe[stripe_positions] return xs, ys, zs # If video frames are available, extract indices on all frames in the given range else: xs_array = [] ys_array = [] zs_array = [] # Go through all frames in the frame range for fr in range(frame_min, frame_max + 1): # Break the loop if outside frame size if fr == (img_handle.total_frames - 1): break # Set the frame number img_handle.setFrame(fr) # Load the frame fr_img = img_handle.loadFrame() # Apply the dark frame if dark is not None: fr_img = Image.applyDark(fr_img, dark) # Apply the flat to frame if flat_struct is not None: fr_img = Image.applyFlat(fr_img, flat_struct) # Mask the image fr_img = MaskImage.applyMask(fr_img, mask) # Threshold the frame img_thres = Image.thresholdImg(fr_img, img_handle.ff.avepixel, img_handle.ff.stdpixel, \ config.k1_det, config.j1_det, mask=mask, mask_ave_bright=False) # Remove lonely pixels img_thres = morph.clean(img_thres) # Extract the stripe from the thresholded image stripe = np.zeros(img_thres.shape, img_thres.dtype) stripe[stripe_indices] = img_thres[stripe_indices] # Include more pixels for centroiding and photometry and mask out per frame pixels if centroiding: # Dilate the pixels in the stripe twice, to include more pixels for photometry stripe = morph.dilate(stripe) stripe = morph.dilate(stripe) # Get indices of the stripe that is perpendicular to the meteor, and whose thickness is the # length of the meteor on this particular frame - this is called stripe_indices_motion # Compute the previous, current, and the next linear model position of the meteor on the # image model_pos_prev = point1[:2] + (fr - 1 - z1)*motion_vect model_pos = point1[:2] + (fr - z1)*motion_vect model_pos_next = point1[:2] + (fr + 1 - z1)*motion_vect # Get the rho, theta of the line perpendicular to the meteor line x_inters, y_inters = model_pos # Check if the previous, current or the next centroids are outside bounds, and if so, skip the # frame if (not checkCentroidBounds(model_pos_prev, img_w, img_h)) or \ (not checkCentroidBounds(model_pos, img_w, img_h)) or \ (not checkCentroidBounds(model_pos_next, img_w, img_h)): continue # Get parameters of the perpendicular line to the meteor line rho2, theta2 = htLinePerpendicular(rho, theta, x_inters, y_inters, img_h, img_w) # Compute the image indices of this position which will be the intersection with the stripe # The width of the line will be 2x the angular velocity stripe_length = 6*ang_vel if stripe_length < stripe_width_factor*config.stripe_width: stripe_length = stripe_width_factor*config.stripe_width stripe_indices_motion = getStripeIndices(rho2, theta2, stripe_length, img_h, img_w) # Mark only those parts which overlap both lines, which effectively creates a mask for # photometry an centroiding, excluding other influences stripe_new = np.zeros_like(stripe) stripe_new[stripe_indices_motion] = stripe[stripe_indices_motion] stripe = stripe_new if debug: # Show the extracted stripe img_stripe = np.zeros_like(stripe) img_stripe[stripe_indices] = 1 final_stripe = np.zeros_like(stripe) final_stripe[stripe_indices_motion] = img_stripe[stripe_indices_motion] plt.imshow(final_stripe) plt.show() if debug and centroiding: print(fr) print('mean stdpixel3:', np.mean(img_handle.ff.stdpixel)) print('mean avepixel3:', np.mean(img_handle.ff.avepixel)) print('mean frame:', np.mean(fr_img)) fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True, sharey=True) fr_img_noavg = Image.applyDark(fr_img, img_handle.ff.avepixel) #fr_img_noavg = fr_img # Auto levels min_lvl = np.percentile(fr_img_noavg[2:, :], 1) max_lvl = np.percentile(fr_img_noavg[2:, :], 99.0) # Adjust levels fr_img_autolevel = Image.adjustLevels(fr_img_noavg, min_lvl, 1.0, max_lvl) ax1.imshow(stripe, cmap='gray') ax2.imshow(fr_img_autolevel, cmap='gray') plt.show() pass # Get stripe positions (x, y, frame) stripe_positions = stripe.nonzero() xs = stripe_positions[1] ys = stripe_positions[0] zs = np.zeros_like(xs) + fr # Add the points to the list xs_array.append(xs) ys_array.append(ys) zs_array.append(zs) if debug: print('---') print(stripe.nonzero()) print(xs, ys, zs) if len(xs_array) > 0: # Flatten the arrays xs_array = np.concatenate(xs_array) ys_array = np.concatenate(ys_array) zs_array = np.concatenate(zs_array) else: xs_array = np.array(xs_array) ys_array = np.array(ys_array) zs_array = np.array(zs_array) return xs_array, ys_array, zs_array
def detectMeteors(ff_directory, ff_name, config, flat_struct=None): """ Detect meteors on the given FF bin image. Here are the steps in the detection: - input image (FF bin format file) is thresholded (converted to black and white) - several morphological operations are applied to clean the image - image is then broken into several image "windows" (these "windows" are reconstructed from the input FF file, given an input frame range (e.g. 64-128) which helps reduce the noise further) - on each "window" the Kernel-based Hough transform is performed to find any lines on the image - similar lines are joined - stripe around the lines is extracted - 3D line finding (third dimension is time) is applied to check if the line propagates in time - centroiding is performed, which calculates the position and intensity of meteor on each frame Arguments: ff_directory: [string] an absolute path to the input FF bin file ff_name: [string] file name of the FF bin file on which to run the detection on config: [config object] configuration object (loaded from the .config file) Keyword arguments: flat_struct: [Flat struct] Structure containing the flat field. None by default. Return: meteor_detections: [list] a list of detected meteors, with these elements: - rho: [float] meteor line distance from image center (polar coordinates, in pixels) - theta: [float] meteor line angle from image center (polar coordinates, in degrees) - centroids: [list] [frame, X, Y, level] list of meteor points """ t1 = time() t_all = time() # Load the FF bin file ff = FFfile.read(ff_directory, ff_name) # Load the mask file mask = MaskImage.loadMask(config.mask_file) # Mask the FF file ff = MaskImage.applyMask(ff, mask, ff_flag=True) # Apply the flat to maxpixel and avepixel if flat_struct is not None: ff.maxpixel = Image.applyFlat(ff.maxpixel, flat_struct) ff.avepixel = Image.applyFlat(ff.avepixel, flat_struct) # At the end, a check that the detection has a surface brightness above the background will be performed. # The assumption here is that the peak of the meteor should have the intensity which is at least # that of a patch of 4x4 pixels that are of the mean background brightness min_patch_intensity = 4 * 4 * (np.mean(ff.maxpixel - ff.avepixel) + config.k1_det * np.mean(ff.stdpixel) + config.j1) # # Show the maxpixel image # show2(ff_name+' maxpixel', ff.maxpixel) # Get lines on the image line_list = getLines(ff, config.k1_det, config.j1_det, config.time_slide, config.time_window_size, config.max_lines_det, config.max_white_ratio, config.kht_lib_path) logDebug('List of lines:', line_list) # Init meteor list meteor_detections = [] # Only if there are some lines in the image if len(line_list): # Join similar lines line_list = mergeLines(line_list, config.line_min_dist, ff.ncols, ff.nrows) logDebug('Time for finding lines:', time() - t1) logDebug('Number of KHT lines: ', len(line_list)) logDebug(line_list) # Plot lines # plotLines(ff, line_list) # Threshold the image img_thres = thresholdImg(ff, config.k1_det, config.j1_det) filtered_lines = [] # Analyze stripes of each line for line in line_list: rho, theta, frame_min, frame_max = line logDebug('rho, theta, frame_min, frame_max') logDebug(rho, theta, frame_min, frame_max) # Bounded the thresholded image by min and max frames img = selectFrames(np.copy(img_thres), ff, frame_min, frame_max) # Remove lonely pixels img = morph.clean(img) # Get indices of stripe pixels around the line stripe_indices = getStripeIndices(rho, theta, config.stripe_width, img.shape[0], img.shape[1]) # Extract the stripe from the thresholded image stripe = np.zeros((ff.nrows, ff.ncols), np.uint8) stripe[stripe_indices] = img[stripe_indices] # Show stripe #COMMENTED # show2("stripe", stripe*255) # Show 3D could # show3DCloud(ff, stripe) # Get stripe positions stripe_positions = stripe.nonzero() xs = stripe_positions[1] ys = stripe_positions[0] zs = ff.maxframe[stripe_positions] # Limit the number of points to search if too large if len(zs) > config.max_points_det: # Extract weights of each point maxpix_elements = ff.maxpixel[ys, xs].astype(np.float64) weights = maxpix_elements / np.sum(maxpix_elements) # Random sample the point, sampling is weighted by pixel intensity indices = np.random.choice(len(zs), config.max_points_det, replace=False, p=weights) ys = ys[indices] xs = xs[indices] zs = zs[indices] # Make an array to feed into the gropuing algorithm stripe_points = np.vstack((xs, ys, zs)) stripe_points = np.swapaxes(stripe_points, 0, 1) # Sort stripe points by frame stripe_points = stripe_points[stripe_points[:, 2].argsort()] t1 = time() logDebug('finding lines...') # Find a single line in the point cloud detected_line = find3DLines(stripe_points, time(), config, fireball_detection=False) logDebug('time for GROUPING: ', time() - t1) # Extract the first and only line if any if detected_line: detected_line = detected_line[0] # logDebug(detected_line) # Show 3D cloud # show3DCloud(ff, stripe, detected_line, stripe_points, config) # Add the line to the results list filtered_lines.append(detected_line) # Merge similar lines in 3D filtered_lines = merge3DLines(filtered_lines, config.vect_angle_thresh) logDebug('after filtering:') logDebug(filtered_lines) for detected_line in filtered_lines: # Get frame range frame_min = detected_line[4] frame_max = detected_line[5] # Check if the line covers a minimum frame range if (abs(frame_max - frame_min) + 1 < config.line_minimum_frame_range_det): continue # Extand the frame range for several frames, just to be sure to catch all parts of a meteor frame_min -= config.frame_extension frame_max += config.frame_extension # Cap values to 0-255 frame_min = max(frame_min, 0) frame_max = min(frame_max, 255) logDebug(detected_line) # Get coordinates of 2 points that describe the line x1, y1, z1 = detected_line[0] x2, y2, z2 = detected_line[1] # Convert Cartesian line coordinates to polar rho, theta = getPolarLine(x1, y1, x2, y2, ff.nrows, ff.ncols) # Convert Cartesian line coordinate to CAMS compatible polar coordinates (flipped Y axis) rho_cams, theta_cams = getPolarLine(x1, ff.nrows - y1, x2, ff.nrows - y2, ff.nrows, ff.ncols) logDebug('converted rho, theta') logDebug(rho, theta) # Bounded the thresholded image by min and max frames img = selectFrames(np.copy(img_thres), ff, frame_min, frame_max) # Remove lonely pixels img = morph.clean(img) # Get indices of stripe pixels around the line stripe_indices = getStripeIndices(rho, theta, int(config.stripe_width * 1.5), img.shape[0], img.shape[1]) # Extract the stripe from the thresholded image stripe = np.zeros((ff.nrows, ff.ncols), np.uint8) stripe[stripe_indices] = img[stripe_indices] # Show detected line # show('detected line: '+str(frame_min)+'-'+str(frame_max), stripe) # Get stripe positions stripe_positions = stripe.nonzero() xs = stripe_positions[1] ys = stripe_positions[0] zs = ff.maxframe[stripe_positions] # Make an array to feed into the centroiding algorithm stripe_points = np.vstack((xs, ys, zs)) stripe_points = np.swapaxes(stripe_points, 0, 1) # Sort stripe points by frame stripe_points = stripe_points[stripe_points[:, 2].argsort()] # Show 3D cloud # show3DCloud(ff, stripe, detected_line, stripe_points, config) # Get points of the given line line_points = getAllPoints(stripe_points, x1, y1, z1, x2, y2, z2, config, fireball_detection=False) # Skip if no points were returned if not line_points.any(): continue # Skip if the points cover too small a frame range if abs(np.max(line_points[:, 2]) - np.min(line_points[:, 2]) ) + 1 < config.line_minimum_frame_range_det: continue # Calculate centroids centroids = [] for i in range(frame_min, frame_max + 1): # Select pixel indicies belonging to a given frame frame_pixels_inds = np.where(line_points[:, 2] == i) # Get pixel positions in a given frame (pixels belonging to a found line) frame_pixels = line_points[frame_pixels_inds].astype(np.int64) # Get pixel positions in a given frame (pixels belonging to the whole stripe) frame_pixels_stripe = stripe_points[np.where( stripe_points[:, 2] == i)].astype(np.int64) # Skip if there are no pixels in the frame if not len(frame_pixels): continue # Calculate weights for centroiding max_avg_corrected = ff.maxpixel - ff.avepixel flattened_weights = (max_avg_corrected).astype( np.float32) / ff.stdpixel # Calculate centroids by half-frame for half_frame in range(2): # Apply deinterlacing if it is present in the video if config.deinterlace_order >= 0: # Deinterlace by fields (line lixels) half_frame_pixels = frame_pixels[ frame_pixels[:, 1] % 2 == (config.deinterlace_order + half_frame) % 2] # Deinterlace by fields (stripe pixels) half_frame_pixels_stripe = frame_pixels_stripe[ frame_pixels_stripe[:, 1] % 2 == (config.deinterlace_order + half_frame) % 2] # Skip if there are no pixels in the half-frame if not len(half_frame_pixels): continue # Calculate half-frame value frame_no = i + half_frame * 0.5 # No deinterlacing else: # Skip the second half frame if half_frame == 1: continue half_frame_pixels = frame_pixels half_frame_pixels_stripe = frame_pixels_stripe frame_no = i # Get maxpixel-avepixel values of given pixel indices (this will be used as weights) max_weights = flattened_weights[half_frame_pixels[:, 1], half_frame_pixels[:, 0]] # Calculate weighted centroids x_weighted = half_frame_pixels[:, 0] * np.transpose( max_weights) x_centroid = np.sum(x_weighted) / float( np.sum(max_weights)) y_weighted = half_frame_pixels[:, 1] * np.transpose( max_weights) y_centroid = np.sum(y_weighted) / float( np.sum(max_weights)) # Calculate intensity as the sum of threshold passer pixels on the stripe #intensity_values = max_avg_corrected[half_frame_pixels[:,1], half_frame_pixels[:,0]] intensity_values = max_avg_corrected[ half_frame_pixels_stripe[:, 1], half_frame_pixels_stripe[:, 0]] intensity = np.sum(intensity_values) logDebug("centroid: ", frame_no, x_centroid, y_centroid, intensity) centroids.append( [frame_no, x_centroid, y_centroid, intensity]) # Filter centroids centroids = filterCentroids(centroids, config.centroids_max_deviation, config.centroids_max_distance) # Convert to numpy array for easy slicing centroids = np.array(centroids) # Reject the solution if there are too few centroids if len(centroids) < config.line_minimum_frame_range_det: continue # Check that the detection has a surface brightness above the background # The assumption here is that the peak of the meteor should have the intensity which is at least # that of a patch of 4x4 pixels that are of the mean background brightness if np.max(centroids[:, 3]) < min_patch_intensity: continue # Check the detection if it has the proper angular velocity if not checkAngularVelocity(centroids, config): continue # Append the result to the meteor detections meteor_detections.append([rho_cams, theta_cams, centroids]) logDebug('time for processing:', time() - t_all) # # Plot centroids to image # fig, (ax1, ax2) = plt.subplots(nrows=2) # ax1.imshow(ff.maxpixel - ff.avepixel, cmap='gray') # ax1.scatter(centroids[:,1], centroids[:,2], s=5, c='r', edgecolors='none') # # Plot lightcurve # ax2.plot(centroids[:,0], centroids[:,3]) # # # Plot relative angular velocity # # ang_vels = [] # # fr_prev, x_prev, y_prev, _ = centroids[0] # # for fr, x, y, _ in centroids[1:]: # # dx = x - x_prev # # dy = y - y_prev # # dfr = fr - fr_prev # # ddist = np.sqrt(dx**2 + dy**2) # # dt = dfr/config.fps # # ang_vels.append(ddist/dt) # # x_prev = x # # y_prev = y # # fr_prev = fr # # ax2.plot(ang_vels) # plt.show() return meteor_detections
def extractStars(ff_dir, ff_name, config=None, max_global_intensity=150, border=10, neighborhood_size=10, intensity_threshold=5, flat_struct=None): """ Extracts stars on a given FF bin by searching for local maxima and applying PSF fit for star confirmation. Source of one part of the code: http://stackoverflow.com/questions/9111711/get-coordinates-of-local-maxima-in-2d-array-above-certain-value Arguments: ff: [ff bin struct] FF bin file loaded in the FF bin structure config: [config object] configuration object (loaded from the .config file) max_global_intensity: [int] maximum mean intensity of an image before it is discared as too bright border: [int] apply a mask on the detections by removing all that are too close to the given image border (in pixels) neighborhood_size: [int] size of the neighbourhood for the maximum search (in pixels) intensity_threshold: [float] a threshold for cutting the detections which are too faint (0-255) flat_struct: [Flat struct] Structure containing the flat field. None by default. Return: x2, y2, background, intensity: [list of ndarrays] - x2: X axis coordinates of the star - y2: Y axis coordinates of the star - background: background intensity - intensity: intensity of the star """ # Load parameters from config if given if config: max_global_intensity = config.max_global_intensity border = config.border neighborhood_size = config.neighborhood_size intensity_threshold = config.intensity_threshold # Load the FF bin file ff = FFfile.read(ff_dir, ff_name) # Load the mask file mask = MaskImage.loadMask(config.mask_file) # Mask the FF file ff = MaskImage.applyMask(ff, mask, ff_flag=True) # Apply the flat to maxpixel and avepixel if flat_struct is not None: ff.maxpixel = Image.applyFlat(ff.maxpixel, flat_struct) ff.avepixel = Image.applyFlat(ff.avepixel, flat_struct) # Calculate image mean and stddev global_mean = np.mean(ff.avepixel) # Check if the image is too bright and skip the image if global_mean > max_global_intensity: return [[], [], [], []] data = ff.avepixel.astype(np.float32) # Apply a mean filter to the image to reduce noise data = ndimage.filters.convolve(data, weights=np.full((2, 2), 1.0 / 4)) # Locate local maxima on the image data_max = filters.maximum_filter(data, neighborhood_size) maxima = (data == data_max) data_min = filters.minimum_filter(data, neighborhood_size) diff = ((data_max - data_min) > intensity_threshold) maxima[diff == 0] = 0 # Apply a border mask border_mask = np.ones_like(maxima) * 255 border_mask[:border, :] = 0 border_mask[-border:, :] = 0 border_mask[:, :border] = 0 border_mask[:, -border:] = 0 maxima = MaskImage.applyMask(maxima, (True, border_mask)) # Find and label the maxima labeled, num_objects = ndimage.label(maxima) # Skip the image if there are too many maxima to process if num_objects > config.max_stars: print('Too many candidate stars to process! {:d}/{:d}'.format( num_objects, config.max_stars)) return [[], [], [], []] # Find centres of mass of each labeled objects xy = np.array( ndimage.center_of_mass(data, labeled, range(1, num_objects + 1))) # Remove all detection on the border #xy = xy[np.where((xy[:, 1] > border) & (xy[:,1] < ff.ncols - border) & (xy[:,0] > border) & (xy[:,0] < ff.nrows - border))] # Unpack star coordinates y, x = np.hsplit(xy, 2) # # Plot stars before the PSF fit # plotStars(ff, x, y) # Fit a PSF to each star x2, y2, amplitude, intensity = fitPSF(ff, global_mean, x, y, config=config) # x2, y2, amplitude, intensity = list(x), list(y), [], [] # Skip PSF fit # # Plot stars after PSF fit filtering # plotStars(ff, x2, y2) return x2, y2, amplitude, intensity
def extractStars(ff_dir, ff_name, config=None, max_global_intensity=150, border=10, neighborhood_size=10, intensity_threshold=5, flat_struct=None, dark=None, mask=None): """ Extracts stars on a given FF bin by searching for local maxima and applying PSF fit for star confirmation. Source of one part of the code: http://stackoverflow.com/questions/9111711/get-coordinates-of-local-maxima-in-2d-array-above-certain-value Arguments: ff_dir: [str] Path to directory where FF files are. ff_name: [str] Name of the FF file. config: [config object] configuration object (loaded from the .config file) max_global_intensity: [int] maximum mean intensity of an image before it is discared as too bright border: [int] apply a mask on the detections by removing all that are too close to the given image border (in pixels) neighborhood_size: [int] size of the neighbourhood for the maximum search (in pixels) intensity_threshold: [float] a threshold for cutting the detections which are too faint (0-255) flat_struct: [Flat struct] Structure containing the flat field. None by default. dark: [ndarray] Dark frame. None by default. mask: [ndarray] Mask image. None by default. Return: x2, y2, background, intensity, fwhm: [list of ndarrays] - x2: X axis coordinates of the star - y2: Y axis coordinates of the star - background: background intensity - intensity: intensity of the star - Gaussian Full width at half maximum (FWHM) of fitted stars """ # This will be returned if there was an error error_return = [[], [], [], [], [], []] # Load parameters from config if given if config: max_global_intensity = config.max_global_intensity border = config.border neighborhood_size = config.neighborhood_size intensity_threshold = config.intensity_threshold # Load the FF bin file ff = FFfile.read(ff_dir, ff_name) # If the FF file could not be read, skip star extraction if ff is None: return error_return # Apply the dark frame if dark is not None: ff.avepixel = Image.applyDark(ff.avepixel, dark) # Apply the flat if flat_struct is not None: ff.avepixel = Image.applyFlat(ff.avepixel, flat_struct) # Mask the FF file if mask is not None: ff = MaskImage.applyMask(ff, mask, ff_flag=True) # Calculate image mean and stddev global_mean = np.mean(ff.avepixel) # Check if the image is too bright and skip the image if global_mean > max_global_intensity: return error_return data = ff.avepixel.astype(np.float32) # Apply a mean filter to the image to reduce noise data = ndimage.filters.convolve(data, weights=np.full((2, 2), 1.0 / 4)) # Locate local maxima on the image data_max = filters.maximum_filter(data, neighborhood_size) maxima = (data == data_max) data_min = filters.minimum_filter(data, neighborhood_size) diff = ((data_max - data_min) > intensity_threshold) maxima[diff == 0] = 0 # Apply a border mask border_mask = np.ones_like(maxima) * 255 border_mask[:border, :] = 0 border_mask[-border:, :] = 0 border_mask[:, :border] = 0 border_mask[:, -border:] = 0 maxima = MaskImage.applyMask(maxima, border_mask, image=True) # Remove all detections close to the mask image if mask is not None: erosion_kernel = np.ones((5, 5), mask.img.dtype) mask_eroded = cv2.erode(mask.img, erosion_kernel, iterations=1) maxima = MaskImage.applyMask(maxima, mask_eroded, image=True) # Find and label the maxima labeled, num_objects = ndimage.label(maxima) # Skip the image if there are too many maxima to process if num_objects > config.max_stars: print('Too many candidate stars to process! {:d}/{:d}'.format( num_objects, config.max_stars)) return error_return # Find centres of mass of each labeled objects xy = np.array( ndimage.center_of_mass(data, labeled, range(1, num_objects + 1))) # Remove all detection on the border #xy = xy[np.where((xy[:, 1] > border) & (xy[:,1] < ff.ncols - border) & (xy[:,0] > border) & (xy[:,0] < ff.nrows - border))] # Unpack star coordinates y, x = np.hsplit(xy, 2) # # Plot stars before the PSF fit # plotStars(ff, x, y) # Fit a PSF to each star x2, y2, amplitude, intensity, sigma_y_fitted, sigma_x_fitted = fitPSF( ff, global_mean, x, y, config) # x2, y2, amplitude, intensity = list(x), list(y), [], [] # Skip PSF fit # # Plot stars after PSF fit filtering # plotStars(ff, x2, y2) # Compute FWHM from one dimensional sigma sigma_x_fitted = np.array(sigma_x_fitted) sigma_y_fitted = np.array(sigma_y_fitted) sigma_fitted = np.sqrt(sigma_x_fitted**2 + sigma_y_fitted**2) fwhm = 2.355 * sigma_fitted return ff_name, x2, y2, amplitude, intensity, fwhm
def extractStars(ff_dir, ff_name, config=None, max_global_intensity=150, border=10, neighborhood_size=10, intensity_threshold=5, flat_struct=None, dark=None, mask=None): """ Extracts stars on a given FF bin by searching for local maxima and applying PSF fit for star confirmation. Source of one part of the code: http://stackoverflow.com/questions/9111711/get-coordinates-of-local-maxima-in-2d-array-above-certain-value Arguments: ff: [ff bin struct] FF bin file loaded in the FF bin structure config: [config object] configuration object (loaded from the .config file) max_global_intensity: [int] maximum mean intensity of an image before it is discared as too bright border: [int] apply a mask on the detections by removing all that are too close to the given image border (in pixels) neighborhood_size: [int] size of the neighbourhood for the maximum search (in pixels) intensity_threshold: [float] a threshold for cutting the detections which are too faint (0-255) flat_struct: [Flat struct] Structure containing the flat field. None by default. dark: [ndarray] Dark frame. None by default. mask: [ndarray] Mask image. None by default. Return: x2, y2, background, intensity, sigma_fitted: [list of ndarrays] - x2: X axis coordinates of the star - y2: Y axis coordinates of the star - background: background intensity - intensity: intensity of the star - Gaussian stddev of fitted stars """ # This will be returned if there was an error error_return = [[], [], [], []] # Load parameters from config if given if config: max_global_intensity = config.max_global_intensity border = config.border neighborhood_size = config.neighborhood_size intensity_threshold = config.intensity_threshold # Load the FF bin file ff = FFfile.read(ff_dir, ff_name) # If the FF file could not be read, skip star extraction if ff is None: return error_return # Apply the dark frame if dark is not None: ff.avepixel = Image.applyDark(ff.avepixel, dark) # Apply the flat if flat_struct is not None: ff.avepixel = Image.applyFlat(ff.avepixel, flat_struct) # Mask the FF file if mask is not None: ff = MaskImage.applyMask(ff, mask, ff_flag=True) # Calculate image mean and stddev global_mean = np.mean(ff.avepixel) # Check if the image is too bright and skip the image if global_mean > max_global_intensity: return error_return data = ff.avepixel.astype(np.float32) # Apply a mean filter to the image to reduce noise data = ndimage.filters.convolve(data, weights=np.full((2, 2), 1.0/4)) # Locate local maxima on the image data_max = filters.maximum_filter(data, neighborhood_size) maxima = (data == data_max) data_min = filters.minimum_filter(data, neighborhood_size) diff = ((data_max - data_min) > intensity_threshold) maxima[diff == 0] = 0 # Apply a border mask border_mask = np.ones_like(maxima)*255 border_mask[:border,:] = 0 border_mask[-border:,:] = 0 border_mask[:,:border] = 0 border_mask[:,-border:] = 0 maxima = MaskImage.applyMask(maxima, border_mask, image=True) # Find and label the maxima labeled, num_objects = ndimage.label(maxima) # Skip the image if there are too many maxima to process if num_objects > config.max_stars: print('Too many candidate stars to process! {:d}/{:d}'.format(num_objects, config.max_stars)) return error_return # Find centres of mass of each labeled objects xy = np.array(ndimage.center_of_mass(data, labeled, range(1, num_objects+1))) # Remove all detection on the border #xy = xy[np.where((xy[:, 1] > border) & (xy[:,1] < ff.ncols - border) & (xy[:,0] > border) & (xy[:,0] < ff.nrows - border))] # Unpack star coordinates y, x = np.hsplit(xy, 2) # # Plot stars before the PSF fit # plotStars(ff, x, y) # Fit a PSF to each star x2, y2, amplitude, intensity, sigma_y_fitted, sigma_x_fitted = fitPSF(ff, global_mean, x, y, config) # x2, y2, amplitude, intensity = list(x), list(y), [], [] # Skip PSF fit # # Plot stars after PSF fit filtering # plotStars(ff, x2, y2) # Compute one dimensional sigma sigma_x_fitted = np.array(sigma_x_fitted) sigma_y_fitted = np.array(sigma_y_fitted) sigma_fitted = np.sqrt(sigma_x_fitted**2 + sigma_y_fitted**2) return ff_name, x2, y2, amplitude, intensity, sigma_fitted