Kernel-based Moving Object Detection (KBMOD) Demonstration#

GitHub: http://github.com/dirac-institute/kbmod

In this notebook, we demonstrate how our shift-and-stack program KBMOD can search a set of images for a trans-Neptunian object (TNO) that is too faint to detect confidently in any one image. These images were acquired with the Vera C. Rubin Observatory Commissioning Camera (ComCam) as part of the Science Validation process. We made use of the SkyBot service to determine which solar system objects were in the ComCam data. We identified a single TNO, 2013 TA228, which we targeted to test our generalized LSST KBMOD search approach.

Note: The data are currently under embargo, so image data will not be included here; however, images will be shown during the live demo.

Initial Setup#

[1]:
show_embargoed = False  # show pixel data currently under Rubin embargo
[2]:
# Load general libraries needed
from astropy.time import Time
from astropy.wcs import WCS
from astropy.coordinates import EarthLocation, SkyCoord
from astropy.table import Table
from astropy.table import conf
import astropy.units as u

import numpy as np

from datetime import datetime

from matplotlib import pyplot as plt

import glob

import os

os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"  # Suppress GPU logging

# Import the needed KBMOD components
import kbmod
from kbmod.configuration import SearchConfiguration
import kbmod.reprojection as reprojection
from kbmod.reprojection_utils import correct_parallax_geometrically_vectorized
from kbmod.filters.known_object_filters import KnownObjsMatcher
from kbmod.filters.stamp_filters import filter_stamps_by_cnn
from kbmod.results import Results
from kbmod.analysis.plotting import *
from kbmod.analysis.plotting import plot_ic_image_bounds, plot_wcs_on_sky
from kbmod.analysis.visualizer import Visualizer

# Import the LSST Butler interface for accessing ComCam data
import lsst.daf.butler as dafButler
[3]:
# Confirm that a GPU is available and that at least 30 Gb of RAM is available
kbmod.search.validate_gpu(30 * 1024 * 1024 * 1024)
[3]:
True
[4]:
# Working directories
basedir = f"rubin-user/lincc_fw_kbmod_demo"  # Root directory for the demo.
workdir = f"{basedir}/20X20_patch250060"  # The subdirectory where images and other input and output files will be located

# The file contains the configuration parameters used for the KBMOD search.
kbmod_config_path = f"{workdir}/search_config_deep_20250122_39au_a.yaml"

# The KBMOD ImageCollection containing Butler-derived metadata.
full_comcam_ic_path = (
    f"{basedir}/LSSTComCam_runs_DRP_20241101_20241211_w_2024_50_DM-48128.collection"
)

# This is an ImageCollection, output by kbmod.ImageCollection
ic_path = f"{workdir}/tno_2013_TA228_ComCam_w50_20x20_patch250060_betterIDs.collection"

# Define the filename of the WorkUnit that combines the metadata from the ImageCollection and the image data from the Butler.
wu_filename = "tno_2013_TA228_ComCam_w50_20x20_patch110571_wu.fits"

# Reprojected (i.e., reflex-corrected) configuration.
repro_shard_dir = os.path.join(
    workdir, "repro"
)  # The directory to store the reprojected WorkUnit.
repro_workunit_filename = wu_filename + ".repro"

# Paths for results filtering and analysis
ml_model_path = f"{basedir}/kbmod_comcam_05.h5"  # the CNN model
skybot_table_path = f"/sdf/home/c/colinc/skytable.csv"  # solar system objects viewable in the ComCam fields found via SkyBot service

# Create any missing directories
for dirname in [basedir, workdir, repro_shard_dir]:
    os.makedirs(dirname, exist_ok=True)

Exploring the Rubin Solar System Science Validation Field#

Here we load a KBMOD ImageCollection, a table of metadata derived (in this case) from LSST ComCam Butler Collection of difference image data and optimized for efficient analyses. The goal of this section is to visualize the on-sky availability of image data we can search.

[5]:
comcam_ic = kbmod.ImageCollection.read(full_comcam_ic_path)
print(
    f'The full ComCam Butler contains {len(set(comcam_ic["visit"]))} science exposures.'
)
The full ComCam Butler contains 1943 science exposures.

Narrowing down the search area#

Rubin observed an area of the sky (labeled “Rubin_SV_38_7”) near the plane of the solar system (ecliptic), where we are most likely to find TNOs.

[6]:
ecliptic_comcam_field = "Rubin_SV_38_7"  # This is the label for the ecliptic field
ecliptic_ic = comcam_ic[comcam_ic["object"] == ecliptic_comcam_field]
print(
    f'There were {len(set(ecliptic_ic["visit"]))} exposures for {ecliptic_comcam_field}.'
)
There were 175 exposures for Rubin_SV_38_7.

Next we visualize the on-sky area covered by the ecliptic ComCam observations.

[7]:
_ = plot_ic_image_bounds(ecliptic_ic, lw=0.1)
../../_images/notebooks_kbmod_kbmod_usdf_12_0.png

Load the ImageCollection and generate WorkUnit#

This ImageCollection is a collection of ComCam image metadata that overlaps with a 20’ X 20’ patch of the sky as generated by our Region Search toolset. We chose 20’ X 20’ patches as an optimal size to fit on the available GPU architecture. We use the ImageCollection to gather Butler-derived images into a KBMOD WorkUnit. Note that (at present) it takes roughly 4 seconds per image retrieval through the USDF Butler.

[8]:
# Load the ImageCollection
ic = kbmod.ImageCollection.read(ic_path)
print(f"Read {len(ic)} rows from {ic_path}.")
Read 249 rows from rubin-user/lincc_fw_kbmod_demo/20X20_patch250060/tno_2013_TA228_ComCam_w50_20x20_patch250060_betterIDs.collection.
[9]:
# Example row from the ImageCollection, trimmed to remove bulky columns (e.g., WCS)
display_ic = ic.data.copy()
display_ic.remove_columns(["wcs", "global_wcs", "config"])
display_ic[0]
[9]:
Row index=0
dataIdstd_idxvisitdetectorbandfiltermjd_startmjd_midobjectpointing_rapointing_decairmasswcs_errradecra_bldec_blra_tldec_tlra_trdec_trra_brdec_brpsfSigmapsfAreanPsfStarzeroPointskyBgskyNoisemeanVarOBSIDexposureTimeext_idxstd_namecollectiondatasetTypeobs_lonobs_latobs_elevastromOffsetMeanastromOffsetStdDIMM2SEEglobal_wcs_pixel_shape_0global_wcs_pixel_shape_1
str36int64int64int64str1str4float64float64str13float64float64float64float64float64float64float64float64float64float64float64float64float64float64float64float64int64float64float64float64float64str20float64int64str18str56str28float64float64float64float64float64float64int64int64
615b2f69-ba41-4d4b-b29b-a807e7a3ac82020241126001348ii_0660641.0812269964460641.08140639459Rubin_SV_38_738.06539550286286.528268059042921.28850130948918-3.090860900556436e-1238.187289599318736.257539932895328538.133679058832076.40712525766286238.035924326374636.20688385239390638.240928935349346.10778306994699838.338711701856936.308095865824242.187015792731804467.4651836799360810231.763279104942232581.074613153934552.9931925838974752795.876509900088CC_O_20241126_00013430.00ButlerStandardizerLSSTComCam/runs/DRP/20241101_20241211/w_2024_50/DM-48128goodSeeingDiff_differenceExp-70.7494170285165-30.24463897562522662.99616375123nannan0.060006000
[10]:
%%time

wu = ic.toWorkUnit(
    search_config=SearchConfiguration.from_file(kbmod_config_path),
    butler=dafButler.Butler("/repo/embargo"),
)
wu.to_sharded_fits(wu_filename, workdir, overwrite=True)  # save the WorkUnit to disk
print(
    f"WorkUnit {wu_filename} contains {len(wu)} images, spanning {len(set(wu.get_all_obstimes()))} unique timestamps."
)
WorkUnit tno_2013_TA228_ComCam_w50_20x20_patch110571_wu.fits contains 249 images, spanning 57 unique timestamps.
CPU times: user 20min 2s, sys: 2min 33s, total: 22min 36s
Wall time: 28min 58s

Inspect ComCam images from the first exposure in the WorkUnit#

Now that we have materialized our WorkUnit, we can access the data efficiently. Note the artifacts present in the image data example shown here.

[11]:
first_visit_slices = wu.get_unique_obstimes_and_indices()[1][0]
first_wu_visit = wu.get_constituent_meta("visit")[0]

if show_embargoed:
    for visit_slice in first_visit_slices:
        im = wu.im_stack.get_single_image(visit_slice).get_science().image
        vmin, vmax = np.percentile(im, [1, 99])
        plt.imshow(im, cmap="gray", vmin=vmin, vmax=vmax)
        plt.colorbar(label="Pixel Value")
        plt.show()
        plt.close()
        break

Create reflex-corrected WCS for each image#

Create new world coordinate system (WCS) elements for each image in the WorkUnit, correcting for the parallax imparted by Earth’s motion around the Sun for (as yet undiscovered) objects at 39 au.

[12]:
# This is the target search distance in astronomical units for our reflex-correction and reprojection routines
target_search_dist = 39.0  # au
[13]:
%%time

# Find the estimated barycentric distance (EBD) world coordinate system (WCS) for each image
ebd_per_image_wcs, geocentric_dists = kbmod.reprojection_utils.transform_wcses_to_ebd(
    [wu.get_wcs(i) for i in range(len(wu))],
    height=wu.get_wcs(0).array_shape[0],
    width=wu.get_wcs(0).array_shape[1],
    heliocentric_distance=target_search_dist,  # heliocentric reflex correction distance in au
    obstimes=Time(wu.get_all_obstimes(), format="mjd"),
    point_on_earth=EarthLocation.of_site("Rubin Observatory"),
    npoints=10,
)

# Store reflex-corrected WCSs and metadata in the WorkUnit.
wu.org_img_meta["ebd_wcs"] = ebd_per_image_wcs
wu.heliocentric_distance = target_search_dist
wu.org_img_meta["geocentric_distance"] = geocentric_dists
CPU times: user 38.6 s, sys: 715 ms, total: 39.3 s
Wall time: 40.5 s

Reprojecting images to common WCS#

Here, we create mosaics from the input images and handle arbitrary image rotation. We built these features upon the extant AstroPy Reproject functionality.

[14]:
%%time
common_wcs = WCS(ic.data["global_wcs"][0])
common_wcs.pixel_shape = (
    ic.data["global_wcs_pixel_shape_0"][0],
    ic.data["global_wcs_pixel_shape_1"][0],
)

reprojected_wu = reprojection.reproject_work_unit(
    wu,
    common_wcs,
    parallelize=True,
    frame="ebd",
    max_parallel_processes=16,
)
reprojected_wu.to_sharded_fits(repro_workunit_filename, repro_shard_dir, overwrite=True)
Reprojecting: 100%|██████████| 57/57 [42:41]
CPU times: user 29.4 s, sys: 2min 20s, total: 2min 50s
Wall time: 43min 46s
[15]:
desired_slice = 0
chip_WCSs = []

for slice in wu.get_unique_obstimes_and_indices()[1][desired_slice]:
    chip_WCSs.append(ebd_per_image_wcs[slice])

if show_embargoed:
    im = reprojected_wu.im_stack.get_single_image(desired_slice).get_science().image
    vmin, vmax = np.percentile(im, [1, 99])
    plt.imshow(im, cmap="gray", vmin=vmin, vmax=vmax, origin="lower")
    plt.colorbar(label="Pixel Value")

_ = plot_wcs_on_sky([reprojected_wu.get_wcs(desired_slice)] + chip_WCSs)
../../_images/notebooks_kbmod_kbmod_usdf_24_0.png

KBMOD-ML filtering of results with a prototype CNN#

We are looking for very faint objects at a very low signal-to-noise ratio, and, consequently, many false positives are produced during the search phase. Here we employ a simple network trained on a limited set of ComCam data to filter out images unlikely to show a real minor planet.

Each row of the results table represents a trajectory, its associated likelihood, and provenance metadata.

[18]:
%%time

filter_stamps_by_cnn(
    res,
    ml_model_path,
    coadd_type="mean",
)

res.filter_rows(res["cnn_class"])
CPU times: user 1.84 s, sys: 1.96 s, total: 3.8 s
Wall time: 9.76 s
[18]:
Results length=22
xyvxvylikelihoodfluxobs_countpsi_curvephi_curveobs_validstampcoadd_sumcoadd_meancoadd_mediancoadd_weightedpred_xpred_yglobal_raglobal_decimg_raimg_decimg_ximg_ycnn_class
int64int64float64float64float64float64int64float64[57]float64[57]bool[57]float32[21,21]float32[21,21]float32[21,21]float32[21,21]float32[21,21]float64[57]float64[57]float64[57]float64[57]float64[57]float64[57]float64[57]float64[57]bool
5806435656.43665313720703204.6628112792968836.18541069178031408.0334010176237130.021866656839847565 .. 0.00.0007294111419469118 .. 0.0True .. False20.23334 .. -94.7061620.23334 .. -94.706160.20920493 .. -11.737687-0.892758 .. -15.6355950.4706418 .. -13.50112155806.5 .. 6595.2123149900274356.5 .. 7216.69936873332738.509554174106526 .. 38.465349895029676.908724233093626 .. 7.067605472471810537.99950777072311 .. 37.650207531135466.548096974723788 .. 6.637261099587939-3265.7342346515434 .. -4678.760098605894-1234.9678896386515 .. -3509.434930227521True
56994540-90.18154907226562-202.666702270507821.636266696267654139.48144378247687280.0 .. 0.00.0 .. 0.0False .. False-3.855865 .. 159.08072-3.855865 .. 159.08072-0.1676726 .. 5.853974-0.2045532 .. 2.0483239-0.34978858 .. 5.04183445699.5 .. 4439.1967328085374540.5 .. 1708.196611445847338.51553881689849 .. 38.586095446998046.918948322922115 .. 6.76161544779055138.00561554046287 .. 37.773765368205066.558564834967566 .. 6.325401982056455-3249.8332376762755 .. -2203.885892032664-1452.0794408411098 .. 1981.7598218750734True
19495209-17.98046875224.2804107666015621.454490490696276352.7371711329707380.20420870184898376 .. 0.00.0006917000864632428 .. 0.0True .. False0.77013946 .. -29.168180.77013946 .. -29.16818-0.100083984 .. -3.3221240.11093342 .. -2.85020071.5113386 .. -2.14844471949.5 .. 1698.21973541838275209.5 .. 8343.8588279869238.72540442743863 .. 38.7394944407571066.956135081307372 .. 7.13026157576297638.22015755804006 .. 37.929412758325486.596832843939637 .. 6.701618950071004-101.53375772224335 .. 237.1118383536974-3745.320939526413 .. -4907.2114066174245True
9585536-157.93850708007812160.251144409179721.344412889266381402.239672588483280.11928845942020416 .. 0.05.621158197754994e-05 .. 0.0True .. False-63.087055 .. -18.010695-63.087055 .. -18.010695-8.205102 .. -1.4904785-4.993581 .. -0.01013577-5.1580286 .. -3.1463404958.5 .. -1248.71886612147735536.5 .. 7776.03838615278738.78087232587586 .. 38.904469997000326.974291552722317 .. 7.09866193852367538.27685358452782 .. 38.0975678835442866.615469142981776 .. 6.669664016320991662.8313851602777 .. 3263.145364664731-4489.756106036503 .. -4481.656509372213True
59192932-143.79998779296875119.3941116333007816.2013789093890199.0629908946779380.015175960026681423 .. 0.0097219292074441910.0004723590100184083 .. 0.0004808444937225431True .. True-3.399155 .. -0.8875536-3.399155 .. -0.8875536-0.09186905 .. 0.21398003-0.5983776 .. -1.1679907-2.7276354 .. -0.0151198685919.5 .. 3909.86946801231122932.5 .. 4601.05405038886838.50325757649764 .. 38.6156912932986.829611374939618 .. 6.92233355145641637.993156529452705 .. 37.803640873969996.46713967359623 .. 6.489354554698109-2731.2927110080623 .. -1816.8844973030682125.06629170645658 .. -985.4264162302392True
59031394-33.183048248291016-42.0569038391113312.38396177998471896.89468373950923280.0011428374564275146 .. 0.00.0007491682772524655 .. 0.0True .. False-60.04982 .. 6.2468038-60.04982 .. 6.2468038-1.9789535 .. -0.057001058-2.1704392 .. -0.34152177-4.062559 .. 0.063146095903.5 .. 5439.7609696449031394.5 .. 806.747251664363538.50418184716348 .. 38.5301337553477846.744167646762159 .. 6.71152281570089837.99419410776594 .. 37.716834601663326.379712676217073 .. 6.274229064564032-2027.6290900309648 .. -3172.3395758682231531.4937546277333 .. 2947.7501223352206True
57132654-102.30045318603516175.081253051757814.3543427256422989.73874839204488250.03027910739183426 .. 0.00.0007080392097122967 .. 0.0True .. False35.526382 .. 93.5403135.526382 .. 93.54031-0.08953918 .. 2.0856016-0.88798666 .. 1.7052162-1.9852548 .. 1.57180775713.5 .. 4283.8328817049742654.5 .. 5101.29180514283138.51478862818741 .. 38.594758858992696.814170729998362 .. 6.950121740719148538.004963311537836 .. 37.7822647090789946.451352113908676 .. 6.517660155848206-2417.2839971276817 .. -2222.5865098258882288.50252681047675 .. -1475.1252529908672True
41992179-92.6096420288086-162.3479156494140613.97246164441968196.680185040986328-0.023373544216156006 .. 0.00.0008316717576235533 .. 0.0True .. False0.49111068 .. 105.271030.49111068 .. 105.271031.1374611 .. 3.17229461.3948504 .. 1.1366441.7147212 .. 2.0497224199.5 .. 2905.26369974737832179.5 .. -89.3411390086166638.59950149202201 .. 38.671909614363916.787800944826847 .. 6.66175931081915138.091607480699444 .. 37.8613677892700556.424455402650073 .. 6.223701323568043-812.535497521165 .. -552.64199354404447.92798777655954 .. 3730.401219749755True
370311437.95817995071411146.54681015014648412.84258788949104884.71877888998442330.05382294952869415 .. 0.00.00012102875916752964 .. 0.0True .. False-81.15636 .. 44.054718-81.15636 .. 44.054718-2.5628412 .. 1.2438331-0.13909239 .. 0.9206163-0.145747 .. 1.30521583703.5 .. 3814.7169872436481143.5 .. 1793.99999155106138.627256266807585 .. 38.621031236694856.730248533702312 .. 6.76638679250473738.12004764448969 .. 37.80935180641766.365593494038168 .. 6.330323158908446107.06961222278667 .. -1574.3486950846996778.1067934701987 .. 1863.0723171415098True
........................................................................
54732588-54.183082580566406188.80778503417977.60633273390921651.53387784276165270.0 .. 0.003865953069180250.0 .. 0.0004649047623388469False .. True-67.465706 .. 10.961738-67.465706 .. 10.961738-0.9301708 .. 1.0347639-0.9377326 .. -0.53472686-1.8812326 .. -0.614056475473.5 .. 4716.281736338642588.5 .. 5227.12254305565738.52821779098108 .. 38.570554774216736.810508082637383 .. 6.95710803407616638.01869801614823 .. 37.757591816847626.447618023073037 .. 6.524745591460983-2167.0219885572305 .. -2668.225825176851241.83242704988822 .. -1581.1211864437057True
45083928-175.17662048339844-108.302963256835949.38755567046388456.3853515800497380.0 .. 0.00.0 .. 0.0False .. False-35.88205 .. -106.83333-35.88205 .. -106.83333-1.5846661 .. -2.8771472-0.58853585 .. -1.0631350.2804402 .. -0.425636354508.5 .. 2060.37541795416753928.5 .. 2414.94673058643738.58219652076092 .. 38.7191819684329566.884964797791103 .. 6.80088310302022338.07380806127428 .. 37.909296084003286.523859973135948 .. 6.365659378535414-1880.1246743011186 .. 175.16516045383554-1421.9133981668729 .. 1143.1109052471595True
56023887-74.1924362182617235.25522613525390612.07598137082234670.38967159853745370.014998147264122963 .. -0.00370898470282554630.0007270314963534474 .. 0.0004828212258871645True .. True-18.720833 .. 51.60132-18.720833 .. 51.60132-0.35166273 .. 1.0744153-0.5335913 .. 0.095541105-0.7444604 .. 0.357813485602.5 .. 4565.6474428118253887.5 .. 4380.19808670318238.52097825165853 .. 38.578994008644066.8826723955090765 .. 6.91005850296080438.01121708813902 .. 37.766272899402636.521451523437192 .. 6.476776495309364-2867.810380870024 .. -2471.328153343442-894.8503059433689 .. -727.5676770624968True
30475592-140.11123657226562142.1628265380859411.804372341774288413.779370336118480.0703236535191536 .. 0.00.00020506029250100255 .. 0.0True .. False12.979774 .. 9.697371512.979774 .. 9.69737151.5654112 .. 1.38533882.277811 .. -1.50267482.846086 .. 1.74353543047.5 .. 1089.42042564315975592.5 .. 7579.25091082710438.66395212716434 .. 38.7735701808479056.977416355193158 .. 7.08777783253470238.157299601457666 .. 37.9642092367694566.6185459939676 .. 6.658346189215684-1282.6928488196675 .. 895.2788987439203-3606.424934177934 .. -4160.1464381953865True
56635586-101.15679931640625113.430496215820317.816344372545343127.73265703813134110.03316514566540718 .. 0.00.00046595142339356244 .. 0.0True .. False-15.995832 .. -16.329336-15.995832 .. -16.329336-0.84943336 .. -1.6618574-3.3020778 .. -2.7392411-1.0653406 .. -1.41159225663.5 .. 4249.8156489479115586.5 .. 7171.7114590024538.51753534434539 .. 38.596645839119966.977059630723896 .. 7.06514414905178538.00759246876685 .. 37.783982976827366.618028916488452 .. 6.634969077940713-3685.958698169858 .. -2295.1759955894354-2430.559867137835 .. -3583.0576676185215True
3407743-107.7586441040039-111.634696960449228.60184808720700164.26481555737193210.0 .. 0.00.0 .. 0.0False .. False0.1323638 .. 20.1505050.1323638 .. 20.150505-1.4653327 .. 0.905314450.38149494 .. 0.62079966-0.03282839 .. 0.54527873407.5 .. 1901.5536880380037743.5 .. -816.614843430338238.64381584767605 .. 38.728044867041146.70802744892924 .. 6.621351932565926538.13700300973308 .. 37.9186305620649766.342872573334869 .. 6.182579642832663558.0425046661936 .. 504.707703889427931013.1909687166695 .. 4418.382194508643True
40505382-38.51970672607422212.005233764648448.057156627446933262.083330387379270.11224542558193207 .. 0.00.00019485079974401742 .. 0.0True .. False-4.9571695 .. -4.3161826-4.9571695 .. -4.31618260.034389496 .. -0.66260341.4952997 .. -1.45867681.703118 .. -0.94310274050.5 .. 3512.18021355203935382.5 .. 8345.31103533902438.60781578793283 .. 38.637934837792496.965746125844088 .. 7.13034706522683438.0999141024019 .. 37.8259340208158656.606546121722411 .. 6.701536360290903-2110.8363892465536 .. -1606.399658119522-2964.408916516583 .. -4815.316658730674True
3322583-38.88623809814453113.776359558105477.96284577505937860.717427452486156220.0 .. 0.00.0 .. 0.0False .. False16.933348 .. 33.7219516.933348 .. 33.721951.1358886 .. -0.1022914950.8899715 .. 0.164159610.13089246 .. 0.46824718332.5 .. -210.94212788587732583.5 .. 4173.54496102994838.815857855481525 .. 38.846296550190886.810227130746243 .. 6.898552226393892538.31280468662382 .. 38.038645423133966.447620536128645 .. 6.4654729230890182560.2443879652087 .. 2392.675141056863-2053.728039251649 .. -761.2647513842826True
29591306-66.56809997558594-201.595275878906257.38023763875628953.64604529404078260.041234731674194336 .. 0.00.0007394053973257542 .. 0.0True .. False-60.94166 .. 82.30436-60.94166 .. 82.304360.5744564 .. 1.5967670.8632641 .. 0.92922530.14690402 .. 0.216011032959.5 .. 2029.19890456494481306.5 .. -1510.830013226472638.66887637641392 .. 38.720901531163936.7393056350010845 .. 6.5827858952065938.16259341143577 .. 37.9114165919401246.374902579352502 .. 6.143237949601344717.8714769836095 .. 411.3148084089031295.49540366702104 .. 5129.764121475218True
2226599037.91838836669922221.78186035156258.044575738792933190.0887503150492880.059007223695516586 .. 0.00.0003396226966287941 .. 0.0True .. False14.603372 .. 0.014.603372 .. 0.01.739654 .. 0.02.72528 .. 0.02.121596 .. 0.02226.5 .. 2756.41625489700855990.5 .. 9089.94113934982338.709905396104325 .. 38.680249572611376.999525338300865 .. 7.17171481852441238.20426195416528 .. 37.868973091761256.64121717538698 .. 6.74379783599378-706.2963534796628 .. -876.7464955274651-4340.107521270118 .. -5612.347098917529True

Visualize Results#

Here we visualize results in the form of co-added image cutouts. For each row in the results table, we (1) generate a combined co-added image cutout that spans all results above the prescribed likelihood threshold, and (2) provide daily co-added cutouts.

[19]:
if show_embargoed:
    res.table["stamp"] = res.table[
        "coadd_sum"
    ]  # We store the combined stamps in the results table.
    viz = Visualizer(
        reprojected_wu.im_stack, res
    )  # The image data are derived from the reprojected WorkUnit.
    viz.generate_all_stamps(radius=config["stamp_radius"])
    viz.count_num_days()  # This feature enables the daily image co-addition.

    for res_idx in range(10):  # first 10 results
        viz.plot_daily_coadds(res_idx)

Matching to known objects#

For testing and validation in Rubin data we will make use of known small solar system bodies (e.g., TNOs) we previously identified in ComCam data with SkyBot. In the future we will make use of Rubin’s MPCSky routine to greatly optimize this process.

[20]:
skytable = Table.read(skybot_table_path)
print(f"Read {skybot_table_path}. There are {len(skytable)} rows.")
Read /sdf/home/c/colinc/skytable.csv. There are 9544 rows.
[21]:
known_objs_matcher = KnownObjsMatcher(
    skytable,
    np.array(reprojected_wu.get_all_obstimes()),
    matcher_name="known_matcher",
    sep_thresh=5.0,  # Observations must be within 5 arcsecs.
    time_thresh_s=30,  # Observations must match within 30 seconds.
    name_col="Name",
    ra_col=f"ra_{target_search_dist}",
    dec_col=f"dec_{target_search_dist}",
    mjd_col="mjd_mid",
)

# Carry out initial matching to known objects and populate the matches column.
known_objs_matcher.match(res, reprojected_wu.wcs)

# Filter the matches down to results with at least 10 observations.
min_obs = 10
known_objs_matcher.match_on_min_obs(res, min_obs)

matched_col_name = known_objs_matcher.match_min_obs_col(min_obs)

known_tno_results = []
for res_idx in range(len(res)):
    my_matches = res[res_idx][matched_col_name]
    if len(my_matches) >= 1:
        print(f"Result {res_idx} matched to objects: {my_matches}")
        if "2013 TA228" in my_matches:
            known_tno_results.append(res_idx)
        if show_embargoed:
            viz.plot_daily_coadds(res_idx)
Result 14 matched to objects: {'2013 TA228'}

Here we plot our KBMOD TNO trajectory against the cached SkyBot ephemeris results.

[22]:
tnotable = skytable[skytable["Name"] == "2013 TA228"]
tnotable.sort("mjd_mid")

tno_res_idx = known_tno_results[0]

plt.plot(res[tno_res_idx]["global_ra"], res[tno_res_idx]["global_dec"], zorder=1)
for i, day_obs in enumerate(set(tnotable["day_obs"])):
    tno_day_table = tnotable[tnotable["day_obs"] == day_obs]
    plt.scatter(
        tno_day_table[f"ra_{target_search_dist}"],
        tno_day_table[f"dec_{target_search_dist}"],
        label=f"{day_obs}",
        marker=f"${i}$",
    )
_ = plt.legend()
../../_images/notebooks_kbmod_kbmod_usdf_36_0.png

Matching against known objects to recover the TNO#

Once again we match the search results against known objects, this time with the output from the pencil search. We then display the combined co-added images and daily cutouts for both the updated search result with the initial result to facilitate comparison. The results from the pencil search (result 9 below) indicate a better match to the object’s actual trajectory with the object appearing closer to the center of each cutout.

[24]:
# Carry out initial matching to known objects and populate the matches column.
known_objs_matcher.match(pencil_res, reprojected_wu.wcs)
known_objs_matcher.match_on_min_obs(pencil_res, min_obs)

# Instantiate a KBMOD Vizualizer for the pencil search.
pencil_res.table["stamp"] = pencil_res.table[
    "coadd_sum"
]  # Store combined stamps in the results table.
pencil_viz = Visualizer(
    reprojected_wu.im_stack, pencil_res
)  # Image data are derived from the reprojected WorkUnit.
pencil_viz.generate_all_stamps(radius=config["stamp_radius"])
pencil_viz.count_num_days()  # Enable the daily image co-addition.

# Show the cutouts from our pencil search.
for res_idx in range(len(pencil_res)):
    my_matches = pencil_res[res_idx][matched_col_name]
    if len(my_matches) >= 1:
        print(f"Result {res_idx} matched to objects: {my_matches}")
        if show_embargoed:
            pencil_viz.plot_daily_coadds(res_idx)

# Display the initial known TNO result from the earlier search for comparison.
if show_embargoed:
    viz.plot_daily_coadds(tno_res_idx)
Result 11 matched to objects: {'2013 TA228'}

Next Steps#

We next send results for orbit fitting; the upcoming selected incubator will help automate and optimize this process.

Summary & Key Takeaways#

  • KBMOD represents a novel way of working with Rubin images.

  • KBMOD will lead to software facilitating scalable image science.

  • Unique in LINCC Frameworks, KBMOD directly produces discoveries.

  • Our work has demonstrated how KBMOD facilitates probing two magnitudes fainter.

    • This effectively transforms the 6.7 m Rubin into a 16 m telescope in terms of TNO discovery.

    • This will enhance the LSST discovery of TNOs by a factor of about 5X.

  • KBMOD has proven success with multiple surveys (e.g., DEEP, KLASSI).

  • KBMOD works with Rubin ComCam and ready for LSSTCam.

    • New: spanning multiple nights, filters, and rotation angles

    • We successfully recovered one TNO (as shown in this notebook) and one Jupiter Trojan in ComCam data.

  • KBMOD is capable of LSST-scale deployment.