def show3DCloud(ff, stripe, detected_line=None, stripe_points=None, config=None): """ Shows 3D point cloud of stripe points. """ if detected_line is None: detected_line = [] stripe_indices = stripe.nonzero() xs = stripe_indices[1] ys = stripe_indices[0] zs = ff.maxframe[stripe_indices] logDebug('points: ', len(xs)) # Plot points in 3D fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(xs, ys, zs) if detected_line and len(stripe_points): xs = [detected_line[0][1], detected_line[1][1]] ys = [detected_line[0][0], detected_line[1][0]] zs = [detected_line[0][2], detected_line[1][2]] ax.plot(ys, xs, zs, c = 'r') x1, x2 = ys y1, y2 = xs z1, z2 = zs detected_points = getAllPoints(stripe_points, x1, y1, z1, x2, y2, z2, config, fireball_detection=False) if detected_points.any(): detected_points = np.array(detected_points) xs = detected_points[:,0] ys = detected_points[:,1] zs = detected_points[:,2] ax.scatter(xs, ys, zs, c = 'r', s = 40) # Set limits plt.xlim((0, ff.ncols)) plt.ylim((0, ff.nrows)) ax.set_zlim((0, 255)) plt.show() plt.clf() plt.close()
# detected_line = detected_line[0] xs = [detected_line[0][0], detected_line[1][0]] ys = [detected_line[0][1], detected_line[1][1]] zs = [detected_line[0][2], detected_line[1][2]] ax.plot(ys, xs, zs, c=ln_colors[i % 6]) # Plot grouped points for i, detected_line in enumerate(line_list): x1, x2 = detected_line[0][0], detected_line[1][0] y1, y2 = detected_line[0][1], detected_line[1][1] z1, z2 = detected_line[0][2], detected_line[1][2] detected_points = getAllPoints(event_points, x1, y1, z1, x2, y2, z2, config) if not detected_points.any(): continue detected_points = np.array(detected_points) xs = detected_points[:, 1] ys = detected_points[:, 0] zs = detected_points[:, 2] ax.scatter(xs, ys, zs, c=ln_colors[i % 6], s=40) print('Elapsed time: ', elapsed_time) plt.show()
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