Known Object Name Association (KONA)

Known Object Name Association (KONA), takes a given field of view and computes what objects are contained within it. This is a relatively expensive operation, and is more efficiently done with many images at once, preferably as close together in time as possible.

First we do some setup, including importing many needed packages.

import kete
import matplotlib.pyplot as plt
import numpy as np
import datetime
import warnings

import astropy
from astropy.io import fits
from astropy.wcs import WCS

FITs File

First we load a FITs file, and grab frame information from its header. Observer position is loaded from this file, and the edges of the field of view are calculated from the corners of the frame.

frame = fits.open("data/01772a127-w3-int-1b.fits.gz")[0]

# Here we compute a State of the observer, this could also be constructed
# using spice kernels using kete.spice.state
sc_pos = kete.Vector([frame.header['SUN2SCX'],
                        frame.header['SUN2SCY'],
                        frame.header['SUN2SCZ']],
                    kete.Frames.Equatorial)
sc_vel = kete.Vector([frame.header['SCVELX'],
                        frame.header['SCVELY'],
                        frame.header['SCVELZ']],
                    kete.Frames.Equatorial)

# Load the time
time = datetime.datetime.fromisoformat(frame.header['DATIME'])
# add utc timezone to date
time = time.replace(tzinfo=datetime.timezone.utc)
# now it is correctly formatted, load it
time_jd = kete.Time.from_iso(time.isoformat()).jd

# Now there is a final state of the observer
sc_state = kete.State("WISE", time_jd, sc_pos, sc_vel)


# Build the corner position of the FOV in RA/DEC, and build those into vectors
# The WCS will raise a warning because the FITs files produced by WISE use an
# old format.
with warnings.catch_warnings():
    warnings.filterwarnings("ignore")
    frame_wcs = WCS(frame.header)
corners = []
dx, dy = frame_wcs.pixel_shape
for x, y in zip([0, 0, dx, dx], [0, dy, dy, 0]):
    coord = frame_wcs.pixel_to_world(x, y).icrs
    corners.append(kete.Vector.from_ra_dec(coord.ra.deg, coord.dec.deg))

# Build a generic FOV from the corners and the state of the observer
fov = kete.fov.RectangleFOV.from_corners(corners, sc_state)

MPC Orbit Data

Next we must collect orbit information from the Minor Planet Center (MPC). We will load all known objects from their database, convert them to a kete State, and propagate those states to the epoch near the FITs file epoch we opened.

Note

This takes about 10-15 minutes to run on a powerful desktop, as it is performing orbit propagation using full N-Body mechanics of all planets with corrections for general relativity. This is propagating the entire MPC catalog more than 14 years.

# Load orbit data from the MPC
mpc_obs = kete.mpc.fetch_known_orbit_data()

# Convert that data to State objects.
mpc_states = kete.mpc.table_to_states(mpc_obs)

# It takes a while to propagate 1.5 million asteroids 14 years...
mpc_states = kete.propagate_n_body(mpc_states, time_jd)

Geometry Checks

Calculate what is visible in the frame. Note that this actually accepts any number of frames, and it is strongly recommended to give it all your FOVs of interest at one time. It will be significantly more efficient in its computation. Here we only give it one, and then immediately take the single result back out.

visible_obj = kete.fov_state_check(mpc_states, [fov])[0]

Results

Plot the first n_show=20 objects which were found in the field, but note that 181 known objects have landed in this single FITs frame! That is perhaps unsurprising, as this fits frame is on the ecliptic plane.

n_show = 20
print("Found: ", len(visible_obj))
print(f"Showing top: {n_show}")
print(f"{'Name':<15}{'RA':<15}{'DEC':<15}")
print("-"*45)
for state in list(visible_obj)[:n_show]:
    vec = (state.pos - visible_obj.fov.observer.pos).as_equatorial
    print(f"{state.desig:<15s}{vec.ra_hms:<15s}{vec.dec_dms:<15s}")
Found:  181
Showing top: 20
Name           RA             DEC
---------------------------------------------
208            15 13 39.758   -18 50 09.19
2165           15 14 46.674   -18 19 27.81
9133           15 14 12.436   -18 44 42.05
10458          15 13 04.459   -18 56 52.80
27374          15 14 06.714   -18 51 36.23
28707          15 14 01.716   -18 18 21.00
34749          15 13 59.847   -18 50 07.33
40362          15 13 20.144   -18 45 50.84
43978          15 14 17.820   -18 44 08.77
49635          15 13 24.111   -18 39 13.07
55112          15 14 38.351   -18 35 33.58
59071          15 14 00.191   -18 39 04.66
61151          15 14 23.103   -18 51 16.29
78279          15 13 40.360   -18 53 14.05
79491          15 13 17.290   -18 56 03.63
83697          15 15 04.639   -18 54 58.35
94475          15 13 22.331   -18 29 11.80
94926          15 13 21.224   -18 16 25.10
100566         15 13 48.365   -18 28 44.76
101066         15 12 44.206   -18 17 42.31

Plotting

Now take the same results from above an plot the fits file with the overlaid positions. Note again this is only showing the first 20 of 181.

plt.figure(dpi=300)
wcs = kete.irsa.plot_fits_image(frame, cmap='grey')
for state in list(visible_obj)[:n_show]:
    vec = (state.pos - visible_obj.fov.observer.pos).as_equatorial
    kete.irsa.annotate_plot(wcs, vec.ra, vec.dec, state.desig, px_gap=10, length=10)
plt.xlabel("RA")
plt.ylabel("DEC")
plt.title(f"NEOWISE Frame on Ecliptic\n{kete.Time(time_jd).iso}");
plt.savefig("data/kona.png")
plt.close()
KONA labelling of all objects.