def generateThumbnails(dir_path, config, mosaic_type, file_list=None): """ Generates a mosaic of thumbnails from all FF files in the given folder and saves it as a JPG image. Arguments: dir_path: [str] Path of the night directory. config: [Conf object] Configuration. mosaic_type: [str] Type of the mosaic (e.g. "Captured" or "Detected") Keyword arguments: file_list: [list] A list of file names (without full path) which will be searched for FF files. This is used when generating separate thumbnails for captured and detected files. Return: file_name: [str] Name of the thumbnail file. """ if file_list is None: file_list = sorted(os.listdir(dir_path)) # Make a list of all FF files in the night directory ff_list = [] for file_name in file_list: if FFfile.validFFName(file_name): ff_list.append(file_name) # Calculate the dimensions of the binned image bin_w = int(config.width/config.thumb_bin) bin_h = int(config.height/config.thumb_bin) ### RESIZE AND STACK THUMBNAILS ### ########################################################################################################## timestamps = [] stacked_imgs = [] for i in range(0, len(ff_list), config.thumb_stack): img_stack = np.zeros((bin_h, bin_w)) # Stack thumb_stack images using the 'if lighter' method for j in range(config.thumb_stack): if (i + j) < len(ff_list): # Read maxpixel image img = FFfile.read(dir_path, ff_list[i + j]).maxpixel # Resize the image img = cv2.resize(img, (bin_w, bin_h)) # Stack the image img_stack = stackIfLighter(img_stack, img) else: break # Save the timestamp of the first image in the stack timestamps.append(FFfile.filenameToDatetime(ff_list[i])) # Save the stacked image stacked_imgs.append(img_stack) # cv2.imshow('test', img_stack) # cv2.waitKey(0) # cv2.destroyAllWindows() ########################################################################################################## ### ADD THUMBS TO ONE MOSAIC IMAGE ### ########################################################################################################## header_height = 20 timestamp_height = 10 # Calculate the number of rows for the thumbnail image n_rows = int(np.ceil(float(len(ff_list))/config.thumb_stack/config.thumb_n_width)) # Calculate the size of the mosaic mosaic_w = int(config.thumb_n_width*bin_w) mosaic_h = int((bin_h + timestamp_height)*n_rows + header_height) mosaic_img = np.zeros((mosaic_h, mosaic_w), dtype=np.uint8) # Write header text header_text = 'Station: ' + str(config.stationID) + ' Night: ' + os.path.basename(dir_path) \ + ' Type: ' + mosaic_type cv2.putText(mosaic_img, header_text, (0, header_height//2), \ cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 1) for row in range(n_rows): for col in range(config.thumb_n_width): # Calculate image index indx = row*config.thumb_n_width + col if indx < len(stacked_imgs): # Calculate position of the text text_x = col*bin_w text_y = row*bin_h + (row + 1)*timestamp_height - 1 + header_height # Add timestamp text cv2.putText(mosaic_img, timestamps[indx].strftime('%H:%M:%S'), (text_x, text_y), \ cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) # Add the image to the mosaic img_pos_x = col*bin_w img_pos_y = row*bin_h + (row + 1)*timestamp_height + header_height mosaic_img[img_pos_y : img_pos_y + bin_h, img_pos_x : img_pos_x + bin_w] = stacked_imgs[indx] else: break ########################################################################################################## thumb_name = "{:s}_{:s}_{:s}_thumbs.jpg".format(str(config.stationID), os.path.basename(dir_path), \ mosaic_type) # Save the mosaic cv2.imwrite(os.path.join(dir_path, thumb_name), mosaic_img, [int(cv2.IMWRITE_JPEG_QUALITY), 80]) return thumb_name
def recalibrateIndividualFFsAndApplyAstrometry(dir_path, ftpdetectinfo_path, calstars_list, config, platepar, generate_plot=True): """ Recalibrate FF files with detections and apply the recalibrated platepar to those detections. Arguments: dir_path: [str] Path where the FTPdetectinfo file is. ftpdetectinfo_path: [str] Name of the FTPdetectinfo file. calstars_list: [list] A list of entries [[ff_name, star_coordinates], ...]. config: [Config instance] platepar: [Platepar instance] Initial platepar. Keyword arguments: generate_plot: [bool] Generate the calibration variation plot. True by default. Return: recalibrated_platepars: [dict] A dictionary where the keys are FF file names and values are recalibrated platepar instances for every FF file. """ # Use a copy of the config file config = copy.deepcopy(config) # If the given file does not exits, return nothing if not os.path.isfile(ftpdetectinfo_path): print('ERROR! The FTPdetectinfo file does not exist: {:s}'.format(ftpdetectinfo_path)) print(' The recalibration on every file was not done!') return {} # Read the FTPdetectinfo data cam_code, fps, meteor_list = FTPdetectinfo.readFTPdetectinfo(*os.path.split(ftpdetectinfo_path), \ ret_input_format=True) # Convert the list of stars to a per FF name dictionary calstars = {ff_file: star_data for ff_file, star_data in calstars_list} ### Add neighboring FF files for more robust photometry estimation ### ff_processing_list = [] # Make a list of sorted FF files in CALSTARS calstars_ffs = sorted([ff_file for ff_file in calstars]) # Go through the list of FF files with detections and add neighboring FFs for meteor_entry in meteor_list: ff_name = meteor_entry[0] if ff_name in calstars_ffs: # Find the index of the given FF file in the list of calstars ff_indx = calstars_ffs.index(ff_name) # Add neighbours to the processing list for k in range(-(RECALIBRATE_NEIGHBOURHOOD_SIZE//2), RECALIBRATE_NEIGHBOURHOOD_SIZE//2 + 1): k_indx = ff_indx + k if (k_indx > 0) and (k_indx < len(calstars_ffs)): ff_name_tmp = calstars_ffs[k_indx] if ff_name_tmp not in ff_processing_list: ff_processing_list.append(ff_name_tmp) # Sort the processing list of FF files ff_processing_list = sorted(ff_processing_list) ### ### # Globally increase catalog limiting magnitude config.catalog_mag_limit += 1 # Load catalog stars (overwrite the mag band ratios if specific catalog is used) star_catalog_status = StarCatalog.readStarCatalog(config.star_catalog_path,\ config.star_catalog_file, lim_mag=config.catalog_mag_limit, \ mag_band_ratios=config.star_catalog_band_ratios) if not star_catalog_status: print("Could not load the star catalog!") print(os.path.join(config.star_catalog_path, config.star_catalog_file)) return {} catalog_stars, _, config.star_catalog_band_ratios = star_catalog_status # Update the platepar coordinates from the config file platepar.lat = config.latitude platepar.lon = config.longitude platepar.elev = config.elevation prev_platepar = copy.deepcopy(platepar) # Go through all FF files with detections, recalibrate and apply astrometry recalibrated_platepars = {} for ff_name in ff_processing_list: working_platepar = copy.deepcopy(prev_platepar) # Skip this meteor if its FF file was already recalibrated if ff_name in recalibrated_platepars: continue print() print('Processing: ', ff_name) print('------------------------------------------------------------------------------') # Find extracted stars on this image if not ff_name in calstars: print('Skipped because it was not in CALSTARS:', ff_name) continue # Get stars detected on this FF file (create a dictionaly with only one entry, the residuals function # needs this format) calstars_time = FFfile.getMiddleTimeFF(ff_name, config.fps, ret_milliseconds=True) jd = date2JD(*calstars_time) star_dict_ff = {jd: calstars[ff_name]} # Recalibrate the platepar using star matching result, min_match_radius = recalibrateFF(config, working_platepar, jd, star_dict_ff, catalog_stars) # If the recalibration failed, try using FFT alignment if result is None: print() print('Running FFT alignment...') # Run FFT alignment calstars_coords = np.array(star_dict_ff[jd])[:, :2] calstars_coords[:, [0, 1]] = calstars_coords[:, [1, 0]] print(calstars_time) test_platepar = alignPlatepar(config, prev_platepar, calstars_time, calstars_coords, \ show_plot=False) # Try to recalibrate after FFT alignment result, _ = recalibrateFF(config, test_platepar, jd, star_dict_ff, catalog_stars) # If the FFT alignment failed, align the original platepar using the smallest radius that matched # and force save the the platepar if (result is None) and (min_match_radius is not None): print() print("Using the old platepar with the minimum match radius of: {:.2f}".format(min_match_radius)) result, _ = recalibrateFF(config, working_platepar, jd, star_dict_ff, catalog_stars, max_match_radius=min_match_radius, force_platepar_save=True) if result is not None: working_platepar = result # If the alignment succeeded, save the result else: working_platepar = result else: working_platepar = result # Store the platepar if the fit succeeded if result is not None: # Recompute alt/az of the FOV centre working_platepar.az_centre, working_platepar.alt_centre = raDec2AltAz(working_platepar.RA_d, \ working_platepar.dec_d, working_platepar.JD, working_platepar.lat, working_platepar.lon) # Recompute the rotation wrt horizon working_platepar.rotation_from_horiz = rotationWrtHorizon(working_platepar) # Mark the platepar to indicate that it was automatically recalibrated on an individual FF file working_platepar.auto_recalibrated = True recalibrated_platepars[ff_name] = working_platepar prev_platepar = working_platepar else: print('Recalibration of {:s} failed, using the previous platepar...'.format(ff_name)) # Mark the platepar to indicate that autorecalib failed prev_platepar_tmp = copy.deepcopy(prev_platepar) prev_platepar_tmp.auto_recalibrated = False # If the aligning failed, set the previous platepar as the one that should be used for this FF file recalibrated_platepars[ff_name] = prev_platepar_tmp ### Average out photometric offsets within the given neighbourhood size ### # Go through the list of FF files with detections for meteor_entry in meteor_list: ff_name = meteor_entry[0] # Make sure the FF was successfuly recalibrated if ff_name in recalibrated_platepars: # Find the index of the given FF file in the list of calstars ff_indx = calstars_ffs.index(ff_name) # Compute the average photometric offset and the improved standard deviation using all # neighbors photom_offset_tmp_list = [] photom_offset_std_tmp_list = [] neighboring_ffs = [] for k in range(-(RECALIBRATE_NEIGHBOURHOOD_SIZE//2), RECALIBRATE_NEIGHBOURHOOD_SIZE//2 + 1): k_indx = ff_indx + k if (k_indx > 0) and (k_indx < len(calstars_ffs)): # Get the name of the FF file ff_name_tmp = calstars_ffs[k_indx] # Check that the neighboring FF was successfuly recalibrated if ff_name_tmp in recalibrated_platepars: # Get the computed photometric offset and stddev photom_offset_tmp_list.append(recalibrated_platepars[ff_name_tmp].mag_lev) photom_offset_std_tmp_list.append(recalibrated_platepars[ff_name_tmp].mag_lev_stddev) neighboring_ffs.append(ff_name_tmp) # Compute the new photometric offset and improved standard deviation (assume equal sample size) # Source: https://stats.stackexchange.com/questions/55999/is-it-possible-to-find-the-combined-standard-deviation photom_offset_new = np.mean(photom_offset_tmp_list) photom_offset_std_new = np.sqrt(\ np.sum([st**2 + (mt - photom_offset_new)**2 \ for mt, st in zip(photom_offset_tmp_list, photom_offset_std_tmp_list)]) \ / len(photom_offset_tmp_list) ) # Assign the new photometric offset and standard deviation to all FFs used for computation for ff_name_tmp in neighboring_ffs: recalibrated_platepars[ff_name_tmp].mag_lev = photom_offset_new recalibrated_platepars[ff_name_tmp].mag_lev_stddev = photom_offset_std_new ### ### ### Store all recalibrated platepars as a JSON file ### all_pps = {} for ff_name in recalibrated_platepars: json_str = recalibrated_platepars[ff_name].jsonStr() all_pps[ff_name] = json.loads(json_str) with open(os.path.join(dir_path, config.platepars_recalibrated_name), 'w') as f: # Convert all platepars to a JSON file out_str = json.dumps(all_pps, default=lambda o: o.__dict__, indent=4, sort_keys=True) f.write(out_str) ### ### # If no platepars were recalibrated, use the single platepar recalibration procedure if len(recalibrated_platepars) == 0: print('No FF images were used for recalibration, using the single platepar calibration function...') # Use the initial platepar for calibration applyAstrometryFTPdetectinfo(dir_path, os.path.basename(ftpdetectinfo_path), None, platepar=platepar) return recalibrated_platepars ### GENERATE PLOTS ### dt_list = [] ang_dists = [] rot_angles = [] hour_list = [] photom_offset_list = [] photom_offset_std_list = [] first_dt = np.min([FFfile.filenameToDatetime(ff_name) for ff_name in recalibrated_platepars]) for ff_name in recalibrated_platepars: pp_temp = recalibrated_platepars[ff_name] # If the fitting failed, skip the platepar if pp_temp is None: continue # Add the datetime of the FF file to the list ff_dt = FFfile.filenameToDatetime(ff_name) dt_list.append(ff_dt) # Compute the angular separation from the reference platepar ang_dist = np.degrees(angularSeparation(np.radians(platepar.RA_d), np.radians(platepar.dec_d), \ np.radians(pp_temp.RA_d), np.radians(pp_temp.dec_d))) ang_dists.append(ang_dist*60) # Compute rotation difference rot_diff = (platepar.pos_angle_ref - pp_temp.pos_angle_ref + 180)%360 - 180 rot_angles.append(rot_diff*60) # Compute the hour of the FF used for recalibration hour_list.append((ff_dt - first_dt).total_seconds()/3600) # Add the photometric offset to the list photom_offset_list.append(pp_temp.mag_lev) photom_offset_std_list.append(pp_temp.mag_lev_stddev) if generate_plot: # Generate the name the plots plot_name = os.path.basename(ftpdetectinfo_path).replace('FTPdetectinfo_', '').replace('.txt', '') ### Plot difference from reference platepar in angular distance from (0, 0) vs rotation ### plt.figure() plt.scatter(0, 0, marker='o', edgecolor='k', label='Reference platepar', s=100, c='none', zorder=3) plt.scatter(ang_dists, rot_angles, c=hour_list, zorder=3) plt.colorbar(label="Hours from first FF file") plt.xlabel("Angular distance from reference (arcmin)") plt.ylabel("Rotation from reference (arcmin)") plt.title("FOV centre drift starting at {:s}".format(first_dt.strftime("%Y/%m/%d %H:%M:%S"))) plt.grid() plt.legend() plt.tight_layout() plt.savefig(os.path.join(dir_path, plot_name + '_calibration_variation.png'), dpi=150) # plt.show() plt.clf() plt.close() ### ### ### Plot the photometric offset variation ### plt.figure() plt.errorbar(dt_list, photom_offset_list, yerr=photom_offset_std_list, fmt="o", \ ecolor='lightgray', elinewidth=2, capsize=0, ms=2) # Format datetimes plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%H:%M")) # rotate and align the tick labels so they look better plt.gcf().autofmt_xdate() plt.xlabel("UTC time") plt.ylabel("Photometric offset") plt.title("Photometric offset variation") plt.grid() plt.tight_layout() plt.savefig(os.path.join(dir_path, plot_name + '_photometry_variation.png'), dpi=150) plt.clf() plt.close() ### ### ### Apply platepars to FTPdetectinfo ### meteor_output_list = [] for meteor_entry in meteor_list: ff_name, meteor_No, rho, phi, meteor_meas = meteor_entry # Get the platepar that will be applied to this FF file if ff_name in recalibrated_platepars: working_platepar = recalibrated_platepars[ff_name] else: print('Using default platepar for:', ff_name) working_platepar = platepar # Apply the recalibrated platepar to meteor centroids meteor_picks = applyPlateparToCentroids(ff_name, fps, meteor_meas, working_platepar, \ add_calstatus=True) meteor_output_list.append([ff_name, meteor_No, rho, phi, meteor_picks]) # Calibration string to be written to the FTPdetectinfo file calib_str = 'Recalibrated with RMS on: ' + str(datetime.datetime.utcnow()) + ' UTC' # If no meteors were detected, set dummpy parameters if len(meteor_list) == 0: cam_code = '' fps = 0 # Back up the old FTPdetectinfo file try: shutil.copy(ftpdetectinfo_path, ftpdetectinfo_path.strip('.txt') \ + '_backup_{:s}.txt'.format(datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S.%f'))) except: print('ERROR! The FTPdetectinfo file could not be backed up: {:s}'.format(ftpdetectinfo_path)) # Save the updated FTPdetectinfo FTPdetectinfo.writeFTPdetectinfo(meteor_output_list, dir_path, os.path.basename(ftpdetectinfo_path), \ dir_path, cam_code, fps, calibration=calib_str, celestial_coords_given=True) ### ### return recalibrated_platepars
def generateThumbnails(dir_path, config, mosaic_type, file_list=None, no_stack=False): """ Generates a mosaic of thumbnails from all FF files in the given folder and saves it as a JPG image. Arguments: dir_path: [str] Path of the night directory. config: [Conf object] Configuration. mosaic_type: [str] Type of the mosaic (e.g. "Captured" or "Detected") Keyword arguments: file_list: [list] A list of file names (without full path) which will be searched for FF files. This is used when generating separate thumbnails for captured and detected files. Return: file_name: [str] Name of the thumbnail file. no_stack: [bool] Don't stack the images using the config.thumb_stack option. A max of 1000 images are supported with this option. If there are more, stacks will be done according to the config.thumb_stack option. """ if file_list is None: file_list = sorted(os.listdir(dir_path)) # Make a list of all FF files in the night directory ff_list = [] for file_name in file_list: if FFfile.validFFName(file_name): ff_list.append(file_name) # Calculate the dimensions of the binned image bin_w = int(config.width / config.thumb_bin) bin_h = int(config.height / config.thumb_bin) ### RESIZE AND STACK THUMBNAILS ### ########################################################################################################## timestamps = [] stacked_imgs = [] thumb_stack = config.thumb_stack # Check if no stacks should be done (max 1000 images for no stack) if no_stack and (len(ff_list) < 1000): thumb_stack = 1 for i in range(0, len(ff_list), thumb_stack): img_stack = np.zeros((bin_h, bin_w)) # Stack thumb_stack images using the 'if lighter' method for j in range(thumb_stack): if (i + j) < len(ff_list): tmp_file_name = ff_list[i + j] # Read the FF file ff = FFfile.read(dir_path, tmp_file_name) # Skip the FF if it is corruped if ff is None: continue img = ff.maxpixel # Resize the image img = cv2.resize(img, (bin_w, bin_h)) # Stack the image img_stack = stackIfLighter(img_stack, img) else: break # Save the timestamp of the first image in the stack timestamps.append(FFfile.filenameToDatetime(ff_list[i])) # Save the stacked image stacked_imgs.append(img_stack) # cv2.imshow('test', img_stack) # cv2.waitKey(0) # cv2.destroyAllWindows() ########################################################################################################## ### ADD THUMBS TO ONE MOSAIC IMAGE ### ########################################################################################################## header_height = 20 timestamp_height = 10 # Calculate the number of rows for the thumbnail image n_rows = int( np.ceil(float(len(ff_list)) / thumb_stack / config.thumb_n_width)) # Calculate the size of the mosaic mosaic_w = int(config.thumb_n_width * bin_w) mosaic_h = int((bin_h + timestamp_height) * n_rows + header_height) mosaic_img = np.zeros((mosaic_h, mosaic_w), dtype=np.uint8) # Write header text header_text = 'Station: ' + str(config.stationID) + ' Night: ' + os.path.basename(dir_path) \ + ' Type: ' + mosaic_type cv2.putText(mosaic_img, header_text, (0, header_height//2), \ cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 1) for row in range(n_rows): for col in range(config.thumb_n_width): # Calculate image index indx = row * config.thumb_n_width + col if indx < len(stacked_imgs): # Calculate position of the text text_x = col * bin_w text_y = row * bin_h + ( row + 1) * timestamp_height - 1 + header_height # Add timestamp text cv2.putText(mosaic_img, timestamps[indx].strftime('%H:%M:%S'), (text_x, text_y), \ cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) # Add the image to the mosaic img_pos_x = col * bin_w img_pos_y = row * bin_h + ( row + 1) * timestamp_height + header_height mosaic_img[img_pos_y:img_pos_y + bin_h, img_pos_x:img_pos_x + bin_w] = stacked_imgs[indx] else: break ########################################################################################################## # Only add the station ID if the dir name already doesn't start with it dir_name = os.path.basename(os.path.abspath(dir_path)) if dir_name.startswith(config.stationID): prefix = dir_name else: prefix = "{:s}_{:s}".format(config.stationID, dir_name) thumb_name = "{:s}_{:s}_thumbs.jpg".format(prefix, mosaic_type) # Save the mosaic if USING_IMAGEIO: # Use imageio to write the image imwrite(os.path.join(dir_path, thumb_name), mosaic_img, quality=80) else: # Use OpenCV to save the image imwrite(os.path.join(dir_path, thumb_name), mosaic_img, [int(cv2.IMWRITE_JPEG_QUALITY), 80]) return thumb_name
def generateThumbnails(dir_path, config, mosaic_type, file_list=None): """ Generates a mosaic of thumbnails from all FF files in the given folder and saves it as a JPG image. Arguments: dir_path: [str] Path of the night directory. config: [Conf object] Configuration. mosaic_type: [str] Type of the mosaic (e.g. "Captured" or "Detected") Keyword arguments: file_list: [list] A list of file names (without full path) which will be searched for FF files. This is used when generating separate thumbnails for captured and detected files. Return: file_name: [str] Name of the thumbnail file. """ if file_list is None: file_list = sorted(os.listdir(dir_path)) # Make a list of all FF files in the night directory ff_list = [] for file_name in file_list: if FFfile.validFFName(file_name): ff_list.append(file_name) # Calculate the dimensions of the binned image bin_w = int(config.width/config.thumb_bin) bin_h = int(config.height/config.thumb_bin) ### RESIZE AND STACK THUMBNAILS ### ########################################################################################################## timestamps = [] stacked_imgs = [] for i in range(0, len(ff_list), config.thumb_stack): img_stack = np.zeros((bin_h, bin_w)) # Stack thumb_stack images using the 'if lighter' method for j in range(config.thumb_stack): if (i + j) < len(ff_list): tmp_file_name = ff_list[i + j] # Read the FF file ff = FFfile.read(dir_path, tmp_file_name) # Skip the FF if it is corruped if ff is None: continue img = ff.maxpixel # Resize the image img = cv2.resize(img, (bin_w, bin_h)) # Stack the image img_stack = stackIfLighter(img_stack, img) else: break # Save the timestamp of the first image in the stack timestamps.append(FFfile.filenameToDatetime(ff_list[i])) # Save the stacked image stacked_imgs.append(img_stack) # cv2.imshow('test', img_stack) # cv2.waitKey(0) # cv2.destroyAllWindows() ########################################################################################################## ### ADD THUMBS TO ONE MOSAIC IMAGE ### ########################################################################################################## header_height = 20 timestamp_height = 10 # Calculate the number of rows for the thumbnail image n_rows = int(np.ceil(float(len(ff_list))/config.thumb_stack/config.thumb_n_width)) # Calculate the size of the mosaic mosaic_w = int(config.thumb_n_width*bin_w) mosaic_h = int((bin_h + timestamp_height)*n_rows + header_height) mosaic_img = np.zeros((mosaic_h, mosaic_w), dtype=np.uint8) # Write header text header_text = 'Station: ' + str(config.stationID) + ' Night: ' + os.path.basename(dir_path) \ + ' Type: ' + mosaic_type cv2.putText(mosaic_img, header_text, (0, header_height//2), \ cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255,255,255), 1) for row in range(n_rows): for col in range(config.thumb_n_width): # Calculate image index indx = row*config.thumb_n_width + col if indx < len(stacked_imgs): # Calculate position of the text text_x = col*bin_w text_y = row*bin_h + (row + 1)*timestamp_height - 1 + header_height # Add timestamp text cv2.putText(mosaic_img, timestamps[indx].strftime('%H:%M:%S'), (text_x, text_y), \ cv2.FONT_HERSHEY_SIMPLEX, 0.4, (255, 255, 255), 1) # Add the image to the mosaic img_pos_x = col*bin_w img_pos_y = row*bin_h + (row + 1)*timestamp_height + header_height mosaic_img[img_pos_y : img_pos_y + bin_h, img_pos_x : img_pos_x + bin_w] = stacked_imgs[indx] else: break ########################################################################################################## thumb_name = "{:s}_{:s}_{:s}_thumbs.jpg".format(str(config.stationID), os.path.basename(dir_path), \ mosaic_type) # Save the mosaic cv2.imwrite(os.path.join(dir_path, thumb_name), mosaic_img, [int(cv2.IMWRITE_JPEG_QUALITY), 80]) return thumb_name
def FTPdetectinfo2UFOOrbitInput(dir_path, file_name, platepar_path, platepar_dict=None): """ Convert the FTPdetectinfo file into UFOOrbit input CSV file. Arguments: dir_path: [str] Path of the directory which contains the FTPdetectinfo file. file_name: [str] Name of the FTPdetectinfo file. platepar_path: [str] Full path to the platepar file. Keyword arguments: platepar_dict: [dict] Dictionary of Platepar instances where keys are FF file names. This will be used instead of the platepar at platepar_path. None by default. """ # Load the FTPdetecinfo file meteor_list = FTPdetectinfo.readFTPdetectinfo(dir_path, file_name) # Load the platepar file if platepar_dict is None: pp = RMS.Formats.Platepar.Platepar() pp.read(platepar_path) # Init the UFO format list ufo_meteor_list = [] # Go through every meteor in the list for meteor in meteor_list: ff_name, cam_code, meteor_No, n_segments, fps, hnr, mle, binn, px_fm, rho, phi, \ meteor_meas = meteor # Load the platepar from the platepar dictionary, if given if platepar_dict is not None: if ff_name in platepar_dict: pp = platepar_dict[ff_name] else: print('Skipping {:s} becuase no platepar was found for this FF file!'.format(ff_name)) # Convert the FF file name into time dt = FFfile.filenameToDatetime(ff_name) # Extract measurements calib_status, frame_n, x, y, ra, dec, azim, elev, inten, mag = np.array(meteor_meas).T # If the meteor wasn't calibrated, skip it if not np.all(calib_status): print('Meteor {:d} was not calibrated, skipping it...'.format(meteor_No)) continue # Compute the peak magnitude peak_mag = np.min(mag) # Compute the total duration first_frame = np.min(frame_n) last_frame = np.max(frame_n) duration = (last_frame - first_frame)/fps # Compute times of first and last points dt1 = dt + datetime.timedelta(seconds=first_frame/fps) dt2 = dt + datetime.timedelta(seconds=last_frame/fps) ### Fit a great circle to Az/Alt measurements and compute model beg/end RA and Dec ### # Convert the measurement Az/Alt to cartesian coordinates # NOTE: All values that are used for Great Circle computation are: # theta - the zenith angle (90 deg - altitude) # phi - azimuth +N of due E, which is (90 deg - azim) x, y, z = Math.polarToCartesian(np.radians((90 - azim)%360), np.radians(90 - elev)) # Fit a great circle C, theta0, phi0 = GreatCircle.fitGreatCircle(x, y, z) # Get the first point on the great circle phase1 = GreatCircle.greatCirclePhase(np.radians(90 - elev[0]), np.radians((90 - azim[0])%360), \ theta0, phi0) alt1, azim1 = Math.cartesianToPolar(*GreatCircle.greatCircle(phase1, theta0, phi0)) alt1 = 90 - np.degrees(alt1) azim1 = (90 - np.degrees(azim1))%360 # Get the last point on the great circle phase2 = GreatCircle.greatCirclePhase(np.radians(90 - elev[-1]), np.radians((90 - azim[-1])%360),\ theta0, phi0) alt2, azim2 = Math.cartesianToPolar(*GreatCircle.greatCircle(phase2, theta0, phi0)) alt2 = 90 - np.degrees(alt2) azim2 = (90 - np.degrees(azim2))%360 # Compute RA/Dec from Alt/Az _, ra1, dec1 = RMS.Astrometry.ApplyAstrometry.altAzToRADec(pp.lat, pp.lon, pp.UT_corr, [dt1], \ [azim1], [alt1], dt_time=True) _, ra2, dec2 = RMS.Astrometry.ApplyAstrometry.altAzToRADec(pp.lat, pp.lon, pp.UT_corr, [dt2], \ [azim2], [alt2], dt_time=True) ### ### ufo_meteor_list.append([dt1, peak_mag, duration, azim1[0], alt1[0], azim2[0], alt2[0], \ ra1[0][0], dec1[0][0], ra2[0][0], dec2[0][0], cam_code, pp.lon, pp.lat, pp.elev, pp.UT_corr]) # Construct a file name for the UFO file, which is the FTPdetectinfo file without the FTPdetectinfo # part ufo_file_name = file_name.replace('FTPdetectinfo_', '').replace('.txt', '') + '.csv' # Write the UFOorbit file UFOOrbit.writeUFOOrbit(dir_path, ufo_file_name, ufo_meteor_list)
def FTPdetectinfo2UFOOrbitInput(dir_path, file_name, platepar_path, platepar_dict=None): """ Convert the FTPdetectinfo file into UFOOrbit input CSV file. Arguments: dir_path: [str] Path of the directory which contains the FTPdetectinfo file. file_name: [str] Name of the FTPdetectinfo file. platepar_path: [str] Full path to the platepar file. Keyword arguments: platepar_dict: [dict] Dictionary of Platepar instances where keys are FF file names. This will be used instead of the platepar at platepar_path. None by default. """ # Load the FTPdetecinfo file meteor_list = FTPdetectinfo.readFTPdetectinfo(dir_path, file_name) # Load the platepar file if platepar_dict is None: pp = RMS.Formats.Platepar.Platepar() pp.read(platepar_path, use_flat=None) # Init the UFO format list ufo_meteor_list = [] # Go through every meteor in the list for meteor in meteor_list: ff_name, cam_code, meteor_No, n_segments, fps, hnr, mle, binn, px_fm, rho, phi, \ meteor_meas = meteor # Load the platepar from the platepar dictionary, if given if platepar_dict is not None: if ff_name in platepar_dict: pp = platepar_dict[ff_name] else: print( 'Skipping {:s} becuase no platepar was found for this FF file!' .format(ff_name)) continue # Convert the FF file name into time dt = FFfile.filenameToDatetime(ff_name) # Extract measurements calib_status, frame_n, x, y, ra, dec, azim, elev, inten, mag = np.array( meteor_meas).T # If the meteor wasn't calibrated, skip it if not np.all(calib_status): print('Meteor {:d} was not calibrated, skipping it...'.format( meteor_No)) continue # Compute the peak magnitude peak_mag = np.min(mag) # Compute the total duration first_frame = np.min(frame_n) last_frame = np.max(frame_n) duration = (last_frame - first_frame) / fps # Compute times of first and last points dt1 = dt + datetime.timedelta(seconds=first_frame / fps) dt2 = dt + datetime.timedelta(seconds=last_frame / fps) ### Fit a great circle to Az/Alt measurements and compute model beg/end RA and Dec ### # Convert the measurement Az/Alt to cartesian coordinates # NOTE: All values that are used for Great Circle computation are: # theta - the zenith angle (90 deg - altitude) # phi - azimuth +N of due E, which is (90 deg - azim) x, y, z = Math.polarToCartesian(np.radians((90 - azim) % 360), np.radians(90 - elev)) # Fit a great circle C, theta0, phi0 = GreatCircle.fitGreatCircle(x, y, z) # Get the first point on the great circle phase1 = GreatCircle.greatCirclePhase(np.radians(90 - elev[0]), np.radians((90 - azim[0])%360), \ theta0, phi0) alt1, azim1 = Math.cartesianToPolar( *GreatCircle.greatCircle(phase1, theta0, phi0)) alt1 = 90 - np.degrees(alt1) azim1 = (90 - np.degrees(azim1)) % 360 # Get the last point on the great circle phase2 = GreatCircle.greatCirclePhase(np.radians(90 - elev[-1]), np.radians((90 - azim[-1])%360),\ theta0, phi0) alt2, azim2 = Math.cartesianToPolar( *GreatCircle.greatCircle(phase2, theta0, phi0)) alt2 = 90 - np.degrees(alt2) azim2 = (90 - np.degrees(azim2)) % 360 # Compute RA/Dec from Alt/Az _, ra1, dec1 = RMS.Astrometry.ApplyAstrometry.altAzToRADec(pp.lat, pp.lon, pp.UT_corr, [dt1], \ [azim1], [alt1], dt_time=True) _, ra2, dec2 = RMS.Astrometry.ApplyAstrometry.altAzToRADec(pp.lat, pp.lon, pp.UT_corr, [dt2], \ [azim2], [alt2], dt_time=True) ### ### ufo_meteor_list.append([dt1, peak_mag, duration, azim1[0], alt1[0], azim2[0], alt2[0], \ ra1[0][0], dec1[0][0], ra2[0][0], dec2[0][0], cam_code, pp.lon, pp.lat, pp.elev, pp.UT_corr]) # Construct a file name for the UFO file, which is the FTPdetectinfo file without the FTPdetectinfo # part ufo_file_name = file_name.replace('FTPdetectinfo_', '').replace( '.txt', '') + '.csv' # Write the UFOorbit file UFOOrbit.writeUFOOrbit(dir_path, ufo_file_name, ufo_meteor_list)
def recalibrateIndividualFFsAndApplyAstrometry(dir_path, ftpdetectinfo_path, calstars_list, config, platepar): """ Recalibrate FF files with detections and apply the recalibrated platepar to those detections. Arguments: dir_path: [str] Path where the FTPdetectinfo file is. ftpdetectinfo_path: [str] Name of the FTPdetectinfo file. calstars_list: [list] A list of entries [[ff_name, star_coordinates], ...]. config: [Config instance] platepar: [Platepar instance] Initial platepar. Return: recalibrated_platepars: [dict] A dictionary where the keys are FF file names and values are recalibrated platepar instances for every FF file. """ # Read the FTPdetectinfo data cam_code, fps, meteor_list = FTPdetectinfo.readFTPdetectinfo(*os.path.split(ftpdetectinfo_path), \ ret_input_format=True) # Convert the list of stars to a per FF name dictionary calstars = {ff_file: star_data for ff_file, star_data in calstars_list} # Load catalog stars (overwrite the mag band ratios if specific catalog is used) catalog_stars, _, config.star_catalog_band_ratios = StarCatalog.readStarCatalog(config.star_catalog_path,\ config.star_catalog_file, lim_mag=config.catalog_mag_limit, \ mag_band_ratios=config.star_catalog_band_ratios) prev_platepar = copy.deepcopy(platepar) # Go through all FF files with detections, recalibrate and apply astrometry recalibrated_platepars = {} for meteor_entry in meteor_list: working_platepar = copy.deepcopy(prev_platepar) ff_name, meteor_No, rho, phi, meteor_meas = meteor_entry # Skip this meteors if its FF file was already recalibrated if ff_name in recalibrated_platepars: continue print() print('Processing: ', ff_name) print('------------------------------------------------------------------------------') # Find extracted stars on this image if not ff_name in calstars: print('Skipped because it was not in CALSTARS:', ff_name) continue # Get stars detected on this FF file (create a dictionaly with only one entry, the residuals function # needs this format) calstars_time = FFfile.getMiddleTimeFF(ff_name, config.fps, ret_milliseconds=True) jd = date2JD(*calstars_time) star_dict_ff = {jd: calstars[ff_name]} # Recalibrate the platepar using star matching result = recalibrateFF(config, working_platepar, jd, star_dict_ff, catalog_stars) # If the recalibration failed, try using FFT alignment if result is None: print() print('Running FFT alignment...') # Run FFT alignment calstars_coords = np.array(star_dict_ff[jd])[:, :2] calstars_coords[:, [0, 1]] = calstars_coords[:, [1, 0]] print(calstars_time) working_platepar = alignPlatepar(config, prev_platepar, calstars_time, calstars_coords, \ show_plot=False) # Try to recalibrate after FFT alignment result = recalibrateFF(config, working_platepar, jd, star_dict_ff, catalog_stars) if result is not None: working_platepar = result else: working_platepar = result # Store the platepar if the fit succeeded if result is not None: recalibrated_platepars[ff_name] = working_platepar prev_platepar = working_platepar else: print('Recalibration of {:s} failed, using the previous platepar...'.format(ff_name)) # If the aligning failed, set the previous platepar as the one that should be used for this FF file recalibrated_platepars[ff_name] = prev_platepar ### Store all recalibrated platepars as a JSON file ### all_pps = {} for ff_name in recalibrated_platepars: json_str = recalibrated_platepars[ff_name].jsonStr() all_pps[ff_name] = json.loads(json_str) with open(os.path.join(dir_path, config.platepars_recalibrated_name), 'w') as f: # Convert all platepars to a JSON file out_str = json.dumps(all_pps, default=lambda o: o.__dict__, indent=4, sort_keys=True) f.write(out_str) ### ### # If no platepars were recalibrated, use the single platepar recalibration procedure if len(recalibrated_platepars) == 0: print('No FF images were used for recalibration, using the single platepar calibration function...') # Use the initial platepar for calibration applyAstrometryFTPdetectinfo(dir_path, os.path.basename(ftpdetectinfo_path), None, platepar=platepar) return recalibrated_platepars ### Plot difference from reference platepar in angular distance from (0, 0) vs rotation ### ang_dists = [] rot_angles = [] hour_list = [] first_jd = np.min([FFfile.filenameToDatetime(ff_name) for ff_name in recalibrated_platepars]) for ff_name in recalibrated_platepars: pp_temp = recalibrated_platepars[ff_name] # If the fitting failed, skip the platepar if pp_temp is None: continue # Compute the angular separation from the reference platepar ang_dist = np.degrees(angularSeparation(np.radians(platepar.RA_d), np.radians(platepar.dec_d), \ np.radians(pp_temp.RA_d), np.radians(pp_temp.dec_d))) ang_dists.append(ang_dist*60) rot_angles.append((platepar.pos_angle_ref - pp_temp.pos_angle_ref)*60) # Compute the hour of the FF used for recalibration hour_list.append((FFfile.filenameToDatetime(ff_name) - first_jd).total_seconds()/3600) plt.figure() plt.scatter(0, 0, marker='o', edgecolor='k', label='Reference platepar', s=100, c='none', zorder=3) plt.scatter(ang_dists, rot_angles, c=hour_list, zorder=3) plt.colorbar(label='Hours from first FF file') plt.xlabel("Angular distance from reference (arcmin)") plt.ylabel('Rotation from reference (arcmin)') plt.grid() plt.legend() plt.tight_layout() # Generate the name for the plot calib_plot_name = os.path.basename(ftpdetectinfo_path).replace('FTPdetectinfo_', '').replace('.txt', '') \ + '_calibration_variation.png' plt.savefig(os.path.join(dir_path, calib_plot_name), dpi=150) # plt.show() plt.clf() plt.close() ### ### ### Apply platepars to FTPdetectinfo ### meteor_output_list = [] for meteor_entry in meteor_list: ff_name, meteor_No, rho, phi, meteor_meas = meteor_entry # Get the platepar that will be applied to this FF file if ff_name in recalibrated_platepars: working_platepar = recalibrated_platepars[ff_name] else: print('Using default platepar for:', ff_name) working_platepar = platepar # Apply the recalibrated platepar to meteor centroids meteor_picks = applyPlateparToCentroids(ff_name, fps, meteor_meas, working_platepar, \ add_calstatus=True) meteor_output_list.append([ff_name, meteor_No, rho, phi, meteor_picks]) # Calibration string to be written to the FTPdetectinfo file calib_str = 'Recalibrated with RMS on: ' + str(datetime.datetime.utcnow()) + ' UTC' # If no meteors were detected, set dummpy parameters if len(meteor_list) == 0: cam_code = '' fps = 0 # Back up the old FTPdetectinfo file shutil.copy(ftpdetectinfo_path, ftpdetectinfo_path.strip('.txt') \ + '_backup_{:s}.txt'.format(datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S.%f'))) # Save the updated FTPdetectinfo FTPdetectinfo.writeFTPdetectinfo(meteor_output_list, dir_path, os.path.basename(ftpdetectinfo_path), \ dir_path, cam_code, fps, calibration=calib_str, celestial_coords_given=True) ### ### return recalibrated_platepars