meteortools.fileformats.ftpDetectInfo
1# 2# Load an FTPdetectInfo file - copied from RMS 3# 4# Copyright (C) 2018-2023 Mark McIntyre 5 6import os 7import numpy as np 8import configparser as crp 9import json 10import datetime 11 12try: 13 from ..utils import date2JD, angleBetweenSphericalCoords 14except Exception: 15 from meteortools.utils import date2JD, angleBetweenSphericalCoords 16 17 18def filterFTPforSpecificTime(ftpfile, dtstr): 19 """ filter FTPdetect file for a specific event, by time, and copy it into a new file 20 21 Arguments: 22 ftpfile - [string] full path to the ftpdetect file to filter 23 dtstr - [string] date/time of required event in yyyymmdd_hhmmss format 24 25 Returns: 26 tuple containing 27 full name of the new file containing just matching events 28 the number of matching events 29 """ 30 meteor_list = loadFTPDetectInfo(ftpfile, time_offsets=None, join_broken_meteors=True, locdata=None) 31 refdt = datetime.datetime.strptime(dtstr, '%Y%m%d_%H%M%S') 32 #print(refdt) 33 new_met_list = [] 34 for met in meteor_list: 35 #print(met.ff_name) 36 dtpart = datetime.datetime.strptime(met.ff_name[10:25], '%Y%m%d_%H%M%S') 37 tdiff = (refdt - dtpart).seconds 38 #print(tdiff) 39 if abs(tdiff) < 21: 40 print('adding one entry') 41 new_met_list.append(met) 42 newname = writeNewFTPFile(ftpfile, new_met_list) 43 return newname, len(new_met_list) 44 45 46def writeNewFTPFile(srcname, metlist): 47 """ creates a FTPDetect file from a list of MeteorObservation objects 48 49 Arguments: 50 srcname - [string] full path to the original FTP file 51 metlist - list of MeteorObservation objects 52 53 Returns: 54 the full path to the created file 55 56 """ 57 outdir, fname = os.path.split(srcname) 58 newname = os.path.join(outdir, f'{fname}.old') 59 try: 60 os.rename(srcname, newname) 61 except: 62 pass 63 if os.path.isfile(srcname): 64 srcname = srcname[:-4] + '_new.txt' 65 with open(srcname, 'w') as ftpf: 66 _writeFTPHeader(ftpf, len(metlist), outdir, False) 67 metno = 1 68 ffname = '' 69 for met in metlist: 70 if ffname == met.ff_name: 71 metno = metno + 1 72 else: 73 metno = 1 74 ffname = met.ff_name 75 _writeOneMeteor(ftpf, metno, met.station_id, met.time_data, len(met.frames), met.fps, met.frames, 76 np.degrees(met.ra_data), np.degrees(met.dec_data), 77 np.degrees(met.azim_data), np.degrees(met.elev_data), 78 None, met.mag_data, False, met.x_data, met.y_data, met.ff_name) 79 return srcname 80 81 82def loadFTPDetectInfo(ftpdetectinfo_file_name, time_offsets=None, 83 join_broken_meteors=True, locdata=None): 84 """ Loads an FTPDEtect file into a list of MeteorObservation objects 85 86 Arguments: 87 ftpdetectinfo_file_name: [str] Path to the FTPdetectinfo file. 88 stations: [dict] A dictionary where the keys are stations IDs, and values are lists of: 89 - latitude +N in radians 90 - longitude +E in radians 91 - height in meters 92 93 94 Keyword arguments: 95 time_offsets: [dict] (key, value) pairs of (stations_id, time_offset) for every station. None by 96 default. 97 join_broken_meteors: [bool] Join meteors broken across 2 FF files. 98 99 100 Return: 101 meteor_list: [list] A list of MeteorObservation objects filled with data from the FTPdetectinfo file. 102 103 """ 104 stations={} 105 if locdata is None: 106 dirname, fname = os.path.split(ftpdetectinfo_file_name) 107 cfgfile = os.path.join(dirname, '.config') 108 cfg = crp.ConfigParser() 109 cfg.read(cfgfile) 110 try: 111 lat = float(cfg['System']['latitude'].split()[0]) 112 lon = float(cfg['System']['longitude'].split()[0]) 113 height = float(cfg['System']['elevation'].split()[0]) 114 except: 115 # try reading from platepars file 116 ppf = os.path.join(dirname, 'platepars_all_recalibrated.json') 117 if not os.path.isfile(ppf): 118 return [] 119 js = json.load(open(ppf, 'r')) 120 if len(js) < 10: 121 return [] 122 lat = js[list(js.keys())[0]]['lat'] 123 lon = js[list(js.keys())[0]]['lon'] 124 height = js[list(js.keys())[0]]['elev'] 125 statid= fname.split('_')[1] 126 else: 127 statid = locdata['station_code'] 128 lat = float(locdata['lat']) 129 lon = float(locdata['lon']) 130 height = float(locdata['elev']) 131 132 stations[statid] = [np.radians(lat), np.radians(lon), height*1000] 133 meteor_list = [] 134 135 with open(ftpdetectinfo_file_name) as f: 136 # Skip the header 137 for i in range(11): 138 next(f) 139 140 current_meteor = None 141 142 bin_name = False 143 cal_name = False 144 meteor_header = False 145 146 for line in f: 147 # Skip comments 148 if line.startswith("#"): 149 continue 150 151 line = line.replace('\n', '').replace('\r', '') 152 153 # Skip the line if it is empty 154 if not line: 155 continue 156 157 if '-----' in line: 158 # Mark that the next line is the bin name 159 bin_name = True 160 161 # If the separator is read in, save the current meteor 162 if current_meteor is not None: 163 current_meteor._finish() 164 meteor_list.append(current_meteor) 165 continue 166 167 if bin_name: 168 bin_name = False 169 170 # Mark that the next line is the calibration file name 171 cal_name = True 172 173 # Save the name of the FF file 174 ff_name = line 175 176 # Extract the reference time from the FF bin file name 177 line = line.split('_') 178 179 # Count the number of string segments, and determine if it the old or new CAMS format 180 if len(line) == 6: 181 sc = 1 182 else: 183 sc = 0 184 185 ff_date = line[1 + sc] 186 ff_time = line[2 + sc] 187 milliseconds = line[3 + sc] 188 189 year = ff_date[:4] 190 month = ff_date[4:6] 191 day = ff_date[6:8] 192 193 hour = ff_time[:2] 194 minute = ff_time[2:4] 195 seconds = ff_time[4:6] 196 197 year, month, day, hour, minute, seconds, milliseconds = map(int, [year, month, day, hour, 198 minute, seconds, milliseconds]) 199 200 # Calculate the reference JD time 201 jdt_ref = date2JD(year, month, day, hour, minute, seconds, milliseconds) 202 continue 203 204 if cal_name: 205 cal_name = False 206 # Mark that the next line is the meteor header 207 meteor_header = True 208 continue 209 210 if meteor_header: 211 meteor_header = False 212 line = line.split() 213 214 # Get the station ID and the FPS from the meteor header 215 station_id = line[0].strip() 216 fps = float(line[3]) 217 218 # Try converting station ID to integer 219 try: 220 station_id = int(station_id) 221 except: 222 pass 223 224 # If the time offsets were given, apply the correction to the JD 225 if time_offsets is not None: 226 if station_id in time_offsets: 227 print('Applying time offset for station {:s} of {:.2f} s'.format(str(station_id), 228 time_offsets[station_id])) 229 230 jdt_ref += time_offsets[station_id]/86400.0 231 else: 232 print('Time offset for given station not found!') 233 234 # Get the station data 235 if station_id in stations: 236 lat, lon, height = stations[station_id] 237 else: 238 print('ERROR! No info for station ', station_id, ' found in CameraSites.txt file!') 239 print('Exiting...') 240 break 241 # Init a new meteor observation 242 current_meteor = MeteorObservation(jdt_ref, station_id, lat, lon, height, fps, 243 ff_name=ff_name) 244 continue 245 246 # Read in the meteor observation point 247 if (current_meteor is not None) and (not bin_name) and (not cal_name) and (not meteor_header): 248 249 line = line.replace('\n', '').split() 250 251 # Read in the meteor frame, RA and Dec 252 frame_n = float(line[0]) 253 x = float(line[1]) 254 y = float(line[2]) 255 ra = float(line[3]) 256 dec = float(line[4]) 257 azim = float(line[5]) 258 elev = float(line[6]) 259 260 # Read the visual magnitude, if present 261 if len(line) > 8: 262 mag = line[8] 263 if mag == 'inf': 264 mag = None 265 else: 266 mag = float(mag) 267 else: 268 mag = None 269 270 # Add the measurement point to the current meteor 271 current_meteor._addPoint(frame_n, x, y, azim, elev, ra, dec, mag) 272 273 # Add the last meteor the the meteor list 274 if current_meteor is not None: 275 current_meteor._finish() 276 meteor_list.append(current_meteor) 277 278 # Concatenate observations across different FF files ### 279 if join_broken_meteors: 280 281 # Go through all meteors and compare the next observation 282 merged_indices = [] 283 for i in range(len(meteor_list)): 284 285 # If the next observation was merged, skip it 286 if (i + 1) in merged_indices: 287 continue 288 289 # Get the current meteor observation 290 met1 = meteor_list[i] 291 292 if i >= (len(meteor_list) - 1): 293 break 294 295 # Get the next meteor observation 296 met2 = meteor_list[i + 1] 297 298 # Compare only same station observations 299 if met1.station_id != met2.station_id: 300 continue 301 302 # Extract frame number 303 met1_frame_no = int(met1.ff_name.split("_")[-1].split('.')[0]) 304 met2_frame_no = int(met2.ff_name.split("_")[-1].split('.')[0]) 305 306 # Skip if the next FF is not exactly 256 frames later 307 if met2_frame_no != (met1_frame_no + 256): 308 continue 309 310 311 # Check for frame continouty 312 if (met1.frames[-1] < 254) or (met2.frames[0] > 2): 313 continue 314 315 # Check if the next frame is close to the predicted position 316 317 # Compute angular distance between the last 2 points on the first FF 318 ang_dist = angleBetweenSphericalCoords(met1.dec_data[-2], met1.ra_data[-2], met1.dec_data[-1], 319 met1.ra_data[-1]) 320 321 # Compute frame difference between the last frame on the 1st FF and the first frame on the 2nd FF 322 df = met2.frames[0] + (256 - met1.frames[-1]) 323 324 # Skip the pair if the angular distance between the last and first frames is 2x larger than the 325 # frame difference times the expected separation 326 ang_dist_between = angleBetweenSphericalCoords(met1.dec_data[-1], met1.ra_data[-1], 327 met2.dec_data[0], met2.ra_data[0]) 328 329 if ang_dist_between > 2*df*ang_dist: 330 continue 331 332 # If all checks have passed, merge observations ### 333 # Recompute the frames 334 frames = 256.0 + met2.frames 335 336 # Recompute the time data 337 time_data = frames/met1.fps 338 339 # Add the observations to first meteor object 340 met1.frames = np.append(met1.frames, frames) 341 met1.time_data = np.append(met1.time_data, time_data) 342 met1.x_data = np.append(met1.x_data, met2.x_data) 343 met1.y_data = np.append(met1.y_data, met2.y_data) 344 met1.azim_data = np.append(met1.azim_data, met2.azim_data) 345 met1.elev_data = np.append(met1.elev_data, met2.elev_data) 346 met1.ra_data = np.append(met1.ra_data, met2.ra_data) 347 met1.dec_data = np.append(met1.dec_data, met2.dec_data) 348 met1.mag_data = np.append(met1.mag_data, met2.mag_data) 349 350 # Merge the FF file name and create a list 351 if (met1.ff_name is not None) and (met2.ff_name is not None): 352 met1.ff_name = met1.ff_name + ',' + met2.ff_name 353 354 # Sort all observations by time 355 met1._finish() 356 357 # Indicate that the next observation is to be skipped 358 merged_indices.append(i + 1) 359 360 # Removed merged meteors from the list 361 meteor_list = [element for i, element in enumerate(meteor_list) if i not in merged_indices] 362 363 return meteor_list 364 365 366class MeteorObservation(object): 367 """ Container for meteor observations. 368 """ 369 def __init__(self, jdt_ref, station_id, latitude, longitude, height, fps, ff_name=None): 370 """ Construct the MeteorObservation object. 371 Arguments: 372 jdt_ref: [float] Reference Julian date when the relative time is t = 0s. 373 station_id: [str] Station ID. 374 latitude: [float] Latitude +N in radians. 375 longitude: [float] Longitude +E in radians. 376 height: [float] Elevation above sea level (MSL) in meters. 377 fps: [float] Frames per second. 378 379 Keyword arguments: 380 ff_name: [str] Name of the originating FF file. 381 """ 382 self.jdt_ref = jdt_ref 383 self.station_id = station_id 384 self.latitude = latitude 385 self.longitude = longitude 386 self.height = height 387 self.fps = fps 388 self.ff_name = ff_name 389 self.frames = [] 390 self.time_data = [] 391 self.x_data = [] 392 self.y_data = [] 393 self.azim_data = [] 394 self.elev_data = [] 395 self.ra_data = [] 396 self.dec_data = [] 397 self.mag_data = [] 398 self.abs_mag_data = [] 399 400 def _addPoint(self, frame_n, x, y, azim, elev, ra, dec, mag): 401 """ Adds the measurement point to the meteor. 402 403 Arguments: 404 frame_n: [flaot] Frame number from the reference time. 405 x: [float] X image coordinate. 406 y: [float] X image coordinate. 407 azim: [float] Azimuth, J2000 in degrees. 408 elev: [float] Elevation angle, J2000 in degrees. 409 ra: [float] Right ascension, J2000 in degrees. 410 dec: [float] Declination, J2000 in degrees. 411 mag: [float] Visual magnitude. 412 """ 413 self.frames.append(frame_n) 414 415 # Calculate the time in seconds w.r.t. to the reference JD 416 point_time = float(frame_n)/self.fps 417 self.time_data.append(point_time) 418 self.x_data.append(x) 419 self.y_data.append(y) 420 421 # Angular coordinates converted to radians 422 self.azim_data.append(np.radians(azim)) 423 self.elev_data.append(np.radians(elev)) 424 self.ra_data.append(np.radians(ra)) 425 self.dec_data.append(np.radians(dec)) 426 self.mag_data.append(mag) 427 428 def _finish(self): 429 """ When the initialization is done, convert data lists to numpy arrays. """ 430 self.frames = np.array(self.frames) 431 self.time_data = np.array(self.time_data) 432 self.x_data = np.array(self.x_data) 433 self.y_data = np.array(self.y_data) 434 self.azim_data = np.array(self.azim_data) 435 self.elev_data = np.array(self.elev_data) 436 self.ra_data = np.array(self.ra_data) 437 self.dec_data = np.array(self.dec_data) 438 self.mag_data = np.array(self.mag_data) 439 # Sort by frame 440 temp_arr = np.c_[self.frames, self.time_data, self.x_data, self.y_data, self.azim_data, 441 self.elev_data, self.ra_data, self.dec_data, self.mag_data] 442 temp_arr = temp_arr[np.argsort(temp_arr[:, 0])] 443 self.frames, self.time_data, self.x_data, self.y_data, self.azim_data, self.elev_data, self.ra_data, \ 444 self.dec_data, self.mag_data = temp_arr.T 445 446 447def _writeFTPHeader(ftpf, metcount, fldr, ufo=True): 448 """ 449 Internal function to create the header of the FTPDetect file 450 """ 451 l1 = 'Meteor Count = {:06d}\n'.format(metcount) 452 ftpf.write(l1) 453 ftpf.write('-----------------------------------------------------\n') 454 if ufo is True: 455 ftpf.write('Processed with UFOAnalyser\n') 456 else: 457 ftpf.write('Processed with RMS 1.0\n') 458 ftpf.write('-----------------------------------------------------\n') 459 l1 = 'FF folder = {:s}\n'.format(fldr) 460 ftpf.write(l1) 461 l1 = 'CAL folder = {:s}\n'.format(fldr) 462 ftpf.write(l1) 463 ftpf.write('-----------------------------------------------------\n') 464 ftpf.write('FF file processed\n') 465 ftpf.write('CAL file processed\n') 466 ftpf.write('Cam# Meteor# #Segments fps hnr mle bin Pix/fm Rho Phi\n') 467 ftpf.write('Per segment: Frame# Col Row RA Dec Azim Elev Inten Mag\n') 468 469 470def _writeOneMeteor(ftpf, metno, sta, evttime, fcount, fps, fno, ra, dec, az, alt, b, mag, 471 ufo=True, x=None, y=None, ffname = None): 472 """ Internal function to write one meteor event into the file in FTPDetectInfo style 473 """ 474 ftpf.write('-------------------------------------------------------\n') 475 if ffname is None: 476 ms = '{:03d}'.format(int(evttime.microsecond / 1000)) 477 478 fname = 'FF_' + sta + '_' + evttime.strftime('%Y%m%d_%H%M%S_') + ms + '_0000000.fits\n' 479 else: 480 fname = ffname + '\n' 481 ftpf.write(fname) 482 483 if ufo is True: 484 ftpf.write('UFO UKMON DATA Recalibrated on: ') 485 else: 486 ftpf.write('RMS data reprocessed on: ') 487 ftpf.write(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f UTC\n')) 488 li = f'{sta} {metno:04d} {fcount:04d} {fps:04.2f} 000.0 000.0 00.0 000.0 0000.0 0000.0\n' 489 ftpf.write(li) 490 491 for i in range(len(fno)): 492 # 204.4909 0422.57 0353.46 262.3574 +16.6355 267.7148 +23.0996 000120 3.41 493 bri = 0 494 if b is not None: 495 bri = int(b[i]) 496 if ufo is True: 497 # UFO is timestamped as at the first detection 498 thisfn = fno[i] - fno[0] 499 thisx = 0 500 thisy = 0 501 else: 502 thisfn = fno[i] 503 thisx = x[i] 504 thisy = y[i] 505 li = f'{thisfn:.4f} {thisx:07.2f} {thisy:07.2f} ' 506 li = li + f'{ra[i]:8.4f} {dec[i]:+7.4f} {az[i]:8.4f} ' 507 li = li + f'{alt[i]:+7.4f} {bri:06d} {mag[i]:.02f}\n' 508 ftpf.write(li)
19def filterFTPforSpecificTime(ftpfile, dtstr): 20 """ filter FTPdetect file for a specific event, by time, and copy it into a new file 21 22 Arguments: 23 ftpfile - [string] full path to the ftpdetect file to filter 24 dtstr - [string] date/time of required event in yyyymmdd_hhmmss format 25 26 Returns: 27 tuple containing 28 full name of the new file containing just matching events 29 the number of matching events 30 """ 31 meteor_list = loadFTPDetectInfo(ftpfile, time_offsets=None, join_broken_meteors=True, locdata=None) 32 refdt = datetime.datetime.strptime(dtstr, '%Y%m%d_%H%M%S') 33 #print(refdt) 34 new_met_list = [] 35 for met in meteor_list: 36 #print(met.ff_name) 37 dtpart = datetime.datetime.strptime(met.ff_name[10:25], '%Y%m%d_%H%M%S') 38 tdiff = (refdt - dtpart).seconds 39 #print(tdiff) 40 if abs(tdiff) < 21: 41 print('adding one entry') 42 new_met_list.append(met) 43 newname = writeNewFTPFile(ftpfile, new_met_list) 44 return newname, len(new_met_list)
filter FTPdetect file for a specific event, by time, and copy it into a new file
Arguments:
ftpfile - [string] full path to the ftpdetect file to filter
dtstr - [string] date/time of required event in yyyymmdd_hhmmss format
Returns:
tuple containing
full name of the new file containing just matching events
the number of matching events
47def writeNewFTPFile(srcname, metlist): 48 """ creates a FTPDetect file from a list of MeteorObservation objects 49 50 Arguments: 51 srcname - [string] full path to the original FTP file 52 metlist - list of MeteorObservation objects 53 54 Returns: 55 the full path to the created file 56 57 """ 58 outdir, fname = os.path.split(srcname) 59 newname = os.path.join(outdir, f'{fname}.old') 60 try: 61 os.rename(srcname, newname) 62 except: 63 pass 64 if os.path.isfile(srcname): 65 srcname = srcname[:-4] + '_new.txt' 66 with open(srcname, 'w') as ftpf: 67 _writeFTPHeader(ftpf, len(metlist), outdir, False) 68 metno = 1 69 ffname = '' 70 for met in metlist: 71 if ffname == met.ff_name: 72 metno = metno + 1 73 else: 74 metno = 1 75 ffname = met.ff_name 76 _writeOneMeteor(ftpf, metno, met.station_id, met.time_data, len(met.frames), met.fps, met.frames, 77 np.degrees(met.ra_data), np.degrees(met.dec_data), 78 np.degrees(met.azim_data), np.degrees(met.elev_data), 79 None, met.mag_data, False, met.x_data, met.y_data, met.ff_name) 80 return srcname
creates a FTPDetect file from a list of MeteorObservation objects
Arguments:
srcname - [string] full path to the original FTP file
metlist - list of MeteorObservation objects
Returns: the full path to the created file
83def loadFTPDetectInfo(ftpdetectinfo_file_name, time_offsets=None, 84 join_broken_meteors=True, locdata=None): 85 """ Loads an FTPDEtect file into a list of MeteorObservation objects 86 87 Arguments: 88 ftpdetectinfo_file_name: [str] Path to the FTPdetectinfo file. 89 stations: [dict] A dictionary where the keys are stations IDs, and values are lists of: 90 - latitude +N in radians 91 - longitude +E in radians 92 - height in meters 93 94 95 Keyword arguments: 96 time_offsets: [dict] (key, value) pairs of (stations_id, time_offset) for every station. None by 97 default. 98 join_broken_meteors: [bool] Join meteors broken across 2 FF files. 99 100 101 Return: 102 meteor_list: [list] A list of MeteorObservation objects filled with data from the FTPdetectinfo file. 103 104 """ 105 stations={} 106 if locdata is None: 107 dirname, fname = os.path.split(ftpdetectinfo_file_name) 108 cfgfile = os.path.join(dirname, '.config') 109 cfg = crp.ConfigParser() 110 cfg.read(cfgfile) 111 try: 112 lat = float(cfg['System']['latitude'].split()[0]) 113 lon = float(cfg['System']['longitude'].split()[0]) 114 height = float(cfg['System']['elevation'].split()[0]) 115 except: 116 # try reading from platepars file 117 ppf = os.path.join(dirname, 'platepars_all_recalibrated.json') 118 if not os.path.isfile(ppf): 119 return [] 120 js = json.load(open(ppf, 'r')) 121 if len(js) < 10: 122 return [] 123 lat = js[list(js.keys())[0]]['lat'] 124 lon = js[list(js.keys())[0]]['lon'] 125 height = js[list(js.keys())[0]]['elev'] 126 statid= fname.split('_')[1] 127 else: 128 statid = locdata['station_code'] 129 lat = float(locdata['lat']) 130 lon = float(locdata['lon']) 131 height = float(locdata['elev']) 132 133 stations[statid] = [np.radians(lat), np.radians(lon), height*1000] 134 meteor_list = [] 135 136 with open(ftpdetectinfo_file_name) as f: 137 # Skip the header 138 for i in range(11): 139 next(f) 140 141 current_meteor = None 142 143 bin_name = False 144 cal_name = False 145 meteor_header = False 146 147 for line in f: 148 # Skip comments 149 if line.startswith("#"): 150 continue 151 152 line = line.replace('\n', '').replace('\r', '') 153 154 # Skip the line if it is empty 155 if not line: 156 continue 157 158 if '-----' in line: 159 # Mark that the next line is the bin name 160 bin_name = True 161 162 # If the separator is read in, save the current meteor 163 if current_meteor is not None: 164 current_meteor._finish() 165 meteor_list.append(current_meteor) 166 continue 167 168 if bin_name: 169 bin_name = False 170 171 # Mark that the next line is the calibration file name 172 cal_name = True 173 174 # Save the name of the FF file 175 ff_name = line 176 177 # Extract the reference time from the FF bin file name 178 line = line.split('_') 179 180 # Count the number of string segments, and determine if it the old or new CAMS format 181 if len(line) == 6: 182 sc = 1 183 else: 184 sc = 0 185 186 ff_date = line[1 + sc] 187 ff_time = line[2 + sc] 188 milliseconds = line[3 + sc] 189 190 year = ff_date[:4] 191 month = ff_date[4:6] 192 day = ff_date[6:8] 193 194 hour = ff_time[:2] 195 minute = ff_time[2:4] 196 seconds = ff_time[4:6] 197 198 year, month, day, hour, minute, seconds, milliseconds = map(int, [year, month, day, hour, 199 minute, seconds, milliseconds]) 200 201 # Calculate the reference JD time 202 jdt_ref = date2JD(year, month, day, hour, minute, seconds, milliseconds) 203 continue 204 205 if cal_name: 206 cal_name = False 207 # Mark that the next line is the meteor header 208 meteor_header = True 209 continue 210 211 if meteor_header: 212 meteor_header = False 213 line = line.split() 214 215 # Get the station ID and the FPS from the meteor header 216 station_id = line[0].strip() 217 fps = float(line[3]) 218 219 # Try converting station ID to integer 220 try: 221 station_id = int(station_id) 222 except: 223 pass 224 225 # If the time offsets were given, apply the correction to the JD 226 if time_offsets is not None: 227 if station_id in time_offsets: 228 print('Applying time offset for station {:s} of {:.2f} s'.format(str(station_id), 229 time_offsets[station_id])) 230 231 jdt_ref += time_offsets[station_id]/86400.0 232 else: 233 print('Time offset for given station not found!') 234 235 # Get the station data 236 if station_id in stations: 237 lat, lon, height = stations[station_id] 238 else: 239 print('ERROR! No info for station ', station_id, ' found in CameraSites.txt file!') 240 print('Exiting...') 241 break 242 # Init a new meteor observation 243 current_meteor = MeteorObservation(jdt_ref, station_id, lat, lon, height, fps, 244 ff_name=ff_name) 245 continue 246 247 # Read in the meteor observation point 248 if (current_meteor is not None) and (not bin_name) and (not cal_name) and (not meteor_header): 249 250 line = line.replace('\n', '').split() 251 252 # Read in the meteor frame, RA and Dec 253 frame_n = float(line[0]) 254 x = float(line[1]) 255 y = float(line[2]) 256 ra = float(line[3]) 257 dec = float(line[4]) 258 azim = float(line[5]) 259 elev = float(line[6]) 260 261 # Read the visual magnitude, if present 262 if len(line) > 8: 263 mag = line[8] 264 if mag == 'inf': 265 mag = None 266 else: 267 mag = float(mag) 268 else: 269 mag = None 270 271 # Add the measurement point to the current meteor 272 current_meteor._addPoint(frame_n, x, y, azim, elev, ra, dec, mag) 273 274 # Add the last meteor the the meteor list 275 if current_meteor is not None: 276 current_meteor._finish() 277 meteor_list.append(current_meteor) 278 279 # Concatenate observations across different FF files ### 280 if join_broken_meteors: 281 282 # Go through all meteors and compare the next observation 283 merged_indices = [] 284 for i in range(len(meteor_list)): 285 286 # If the next observation was merged, skip it 287 if (i + 1) in merged_indices: 288 continue 289 290 # Get the current meteor observation 291 met1 = meteor_list[i] 292 293 if i >= (len(meteor_list) - 1): 294 break 295 296 # Get the next meteor observation 297 met2 = meteor_list[i + 1] 298 299 # Compare only same station observations 300 if met1.station_id != met2.station_id: 301 continue 302 303 # Extract frame number 304 met1_frame_no = int(met1.ff_name.split("_")[-1].split('.')[0]) 305 met2_frame_no = int(met2.ff_name.split("_")[-1].split('.')[0]) 306 307 # Skip if the next FF is not exactly 256 frames later 308 if met2_frame_no != (met1_frame_no + 256): 309 continue 310 311 312 # Check for frame continouty 313 if (met1.frames[-1] < 254) or (met2.frames[0] > 2): 314 continue 315 316 # Check if the next frame is close to the predicted position 317 318 # Compute angular distance between the last 2 points on the first FF 319 ang_dist = angleBetweenSphericalCoords(met1.dec_data[-2], met1.ra_data[-2], met1.dec_data[-1], 320 met1.ra_data[-1]) 321 322 # Compute frame difference between the last frame on the 1st FF and the first frame on the 2nd FF 323 df = met2.frames[0] + (256 - met1.frames[-1]) 324 325 # Skip the pair if the angular distance between the last and first frames is 2x larger than the 326 # frame difference times the expected separation 327 ang_dist_between = angleBetweenSphericalCoords(met1.dec_data[-1], met1.ra_data[-1], 328 met2.dec_data[0], met2.ra_data[0]) 329 330 if ang_dist_between > 2*df*ang_dist: 331 continue 332 333 # If all checks have passed, merge observations ### 334 # Recompute the frames 335 frames = 256.0 + met2.frames 336 337 # Recompute the time data 338 time_data = frames/met1.fps 339 340 # Add the observations to first meteor object 341 met1.frames = np.append(met1.frames, frames) 342 met1.time_data = np.append(met1.time_data, time_data) 343 met1.x_data = np.append(met1.x_data, met2.x_data) 344 met1.y_data = np.append(met1.y_data, met2.y_data) 345 met1.azim_data = np.append(met1.azim_data, met2.azim_data) 346 met1.elev_data = np.append(met1.elev_data, met2.elev_data) 347 met1.ra_data = np.append(met1.ra_data, met2.ra_data) 348 met1.dec_data = np.append(met1.dec_data, met2.dec_data) 349 met1.mag_data = np.append(met1.mag_data, met2.mag_data) 350 351 # Merge the FF file name and create a list 352 if (met1.ff_name is not None) and (met2.ff_name is not None): 353 met1.ff_name = met1.ff_name + ',' + met2.ff_name 354 355 # Sort all observations by time 356 met1._finish() 357 358 # Indicate that the next observation is to be skipped 359 merged_indices.append(i + 1) 360 361 # Removed merged meteors from the list 362 meteor_list = [element for i, element in enumerate(meteor_list) if i not in merged_indices] 363 364 return meteor_list
Loads an FTPDEtect file into a list of MeteorObservation objects
Arguments:
ftpdetectinfo_file_name: [str] Path to the FTPdetectinfo file.
stations: [dict] A dictionary where the keys are stations IDs, and values are lists of:
- latitude +N in radians
- longitude +E in radians
- height in meters
Keyword arguments:
time_offsets: [dict] (key, value) pairs of (stations_id, time_offset) for every station. None by
default.
join_broken_meteors: [bool] Join meteors broken across 2 FF files.
Return:
meteor_list: [list] A list of MeteorObservation objects filled with data from the FTPdetectinfo file.
367class MeteorObservation(object): 368 """ Container for meteor observations. 369 """ 370 def __init__(self, jdt_ref, station_id, latitude, longitude, height, fps, ff_name=None): 371 """ Construct the MeteorObservation object. 372 Arguments: 373 jdt_ref: [float] Reference Julian date when the relative time is t = 0s. 374 station_id: [str] Station ID. 375 latitude: [float] Latitude +N in radians. 376 longitude: [float] Longitude +E in radians. 377 height: [float] Elevation above sea level (MSL) in meters. 378 fps: [float] Frames per second. 379 380 Keyword arguments: 381 ff_name: [str] Name of the originating FF file. 382 """ 383 self.jdt_ref = jdt_ref 384 self.station_id = station_id 385 self.latitude = latitude 386 self.longitude = longitude 387 self.height = height 388 self.fps = fps 389 self.ff_name = ff_name 390 self.frames = [] 391 self.time_data = [] 392 self.x_data = [] 393 self.y_data = [] 394 self.azim_data = [] 395 self.elev_data = [] 396 self.ra_data = [] 397 self.dec_data = [] 398 self.mag_data = [] 399 self.abs_mag_data = [] 400 401 def _addPoint(self, frame_n, x, y, azim, elev, ra, dec, mag): 402 """ Adds the measurement point to the meteor. 403 404 Arguments: 405 frame_n: [flaot] Frame number from the reference time. 406 x: [float] X image coordinate. 407 y: [float] X image coordinate. 408 azim: [float] Azimuth, J2000 in degrees. 409 elev: [float] Elevation angle, J2000 in degrees. 410 ra: [float] Right ascension, J2000 in degrees. 411 dec: [float] Declination, J2000 in degrees. 412 mag: [float] Visual magnitude. 413 """ 414 self.frames.append(frame_n) 415 416 # Calculate the time in seconds w.r.t. to the reference JD 417 point_time = float(frame_n)/self.fps 418 self.time_data.append(point_time) 419 self.x_data.append(x) 420 self.y_data.append(y) 421 422 # Angular coordinates converted to radians 423 self.azim_data.append(np.radians(azim)) 424 self.elev_data.append(np.radians(elev)) 425 self.ra_data.append(np.radians(ra)) 426 self.dec_data.append(np.radians(dec)) 427 self.mag_data.append(mag) 428 429 def _finish(self): 430 """ When the initialization is done, convert data lists to numpy arrays. """ 431 self.frames = np.array(self.frames) 432 self.time_data = np.array(self.time_data) 433 self.x_data = np.array(self.x_data) 434 self.y_data = np.array(self.y_data) 435 self.azim_data = np.array(self.azim_data) 436 self.elev_data = np.array(self.elev_data) 437 self.ra_data = np.array(self.ra_data) 438 self.dec_data = np.array(self.dec_data) 439 self.mag_data = np.array(self.mag_data) 440 # Sort by frame 441 temp_arr = np.c_[self.frames, self.time_data, self.x_data, self.y_data, self.azim_data, 442 self.elev_data, self.ra_data, self.dec_data, self.mag_data] 443 temp_arr = temp_arr[np.argsort(temp_arr[:, 0])] 444 self.frames, self.time_data, self.x_data, self.y_data, self.azim_data, self.elev_data, self.ra_data, \ 445 self.dec_data, self.mag_data = temp_arr.T
Container for meteor observations.
370 def __init__(self, jdt_ref, station_id, latitude, longitude, height, fps, ff_name=None): 371 """ Construct the MeteorObservation object. 372 Arguments: 373 jdt_ref: [float] Reference Julian date when the relative time is t = 0s. 374 station_id: [str] Station ID. 375 latitude: [float] Latitude +N in radians. 376 longitude: [float] Longitude +E in radians. 377 height: [float] Elevation above sea level (MSL) in meters. 378 fps: [float] Frames per second. 379 380 Keyword arguments: 381 ff_name: [str] Name of the originating FF file. 382 """ 383 self.jdt_ref = jdt_ref 384 self.station_id = station_id 385 self.latitude = latitude 386 self.longitude = longitude 387 self.height = height 388 self.fps = fps 389 self.ff_name = ff_name 390 self.frames = [] 391 self.time_data = [] 392 self.x_data = [] 393 self.y_data = [] 394 self.azim_data = [] 395 self.elev_data = [] 396 self.ra_data = [] 397 self.dec_data = [] 398 self.mag_data = [] 399 self.abs_mag_data = []
Construct the MeteorObservation object.
Arguments:
jdt_ref: [float] Reference Julian date when the relative time is t = 0s.
station_id: [str] Station ID.
latitude: [float] Latitude +N in radians.
longitude: [float] Longitude +E in radians.
height: [float] Elevation above sea level (MSL) in meters.
fps: [float] Frames per second.
Keyword arguments:
ff_name: [str] Name of the originating FF file.