308.1. 2026 ED138 images and catalogs#
308.01. Rubin data release images, cutouts, and catalogs for 2026 ED138¶
For the Rubin Science Platform at data.lsst.cloud.
Data Release: 2026 ED138 (/rubin/shared/datasets/2026-ED138)
Container Size: Large
LSST Science Pipelines version: r30.0.11
Last verified to run: 2026-10-27
Repository: github.com/lsst/tutorial-notebooks
DOI: 10.11578/rubin/dc.20250909.20
This special release contains proprietary Rubin Observatory LSST data. Access and use are restricted to individuals holding Rubin data rights, under the Rubin Data Policy (RDO-013). Publications using these data must meet the policy's co-authorship requirements for proprietary data and include the proprietary-data acknowledgement (DPOL-306), together with Rubin's standard funding acknowledgement; see citations.lsst.io/acknowledgements.
The MPC submission in the
ades/directory is public through the Minor Planet Center; its presence here does not make the rest of the release public.For more information on the release, see
DATA-RIGHTS.mdandREADME.mdin the release directory.
Learning objective: To find, read, and display the LSSTCam images of trans-Neptunian object 2026 ED138 in its 27 October 2026 data release. This includes running a Butler query by position and time, reading catalogs, generating image cutouts, and detecting sources on the cutouts.
LSST data products: preliminary_visit_image, single_visit_star_detector
Packages: lsst.daf.butler, lsst.geom, lsst.meas.algorithms, astropy
Credit: Originally developed by Mario Juric and Sarah Greenstreet. Please consider acknowledging them if this notebook is used for the preparation of journal articles, software releases, or other notebooks.
Get Support: Everyone is encouraged to ask questions or raise issues in the Support Category of the Rubin Community Forum. Rubin staff will respond to all questions posted there.
1. Introduction¶
This notebook shows how to reach the images in the 2026 ED138 (dwarf planet candidate) 27 October 2026 data release on the Rubin Science Platform. It finds the LSSTCam image behind each of the object's 30 MPC observations, cuts a small stamp around it, and marks the sources detected there, with 2026 ED138 itself in a different color.
Contents of the release in /rubin/shared/datasets/2026-ED138 include:
butler_repo/: a read-only Butler repository with all images and catalogs;ades/2026_ED138.psv: the 30 observations submitted to the Minor Planet Center (ADES PSV);metadata/: observation metadata (ConsDB,ssp.Visit) as Parquet tables;butler_export/: the same Butler data as an export, for importing into another repository.
This directory releases the Rubin Observatory LSSTCam data covering 2026 ED138, a trans-Neptunian object found in Rubin's 2026 observations, so that Rubin data rights holders can study it with the original pixels and catalogs. It holds every LSSTCam image the object falls on, the single-visit source catalogs around its track, the observation metadata needed to tell where Rubin looked and how deep, and the astrometry and photometry submitted to the Minor Planet Center. Everything is in standard Rubin formats: a Butler export that imports into any Butler repository, and Parquet tables.
1.1. Import packages¶
Import standard python packages for array manipulation (numpy) and plotting (matplotlib).
From the astropy package, import the Table and Time modules as well as the ZScaleInterval for visualizing FITS files (astropy documentation).
From the lsst package, import the module for the butler as well as lsst.geom, lsst.afw.table, and SourceDetectionTask from lsst.meas.algorithms that convolves the image with its PSF and finds peaks above 5-sigma.
import numpy as np
import matplotlib.pyplot as plt
import logging
from astropy.table import Table
from astropy.time import Time
from astropy.visualization import ZScaleInterval
from lsst.daf.butler import Butler
import lsst.geom as geom
from lsst.meas.algorithms import SourceDetectionTask
import lsst.afw.table as afwTable
1.2. Define parameters and functions¶
A copy of the 2026 ED138 images and catalogs has been saved in a shared, read-only directory on the RSP for easy access by all RSP users.
Set the path to the data directory.
RELEASE = "/rubin/shared/datasets/2026-ED138"
Create an instance of the Rubin data butler with the "2026-ED138" collection, which contains the full contents of the release.
butler = Butler(f"{RELEASE}/butler_repo", collections="2026-ED138")
The Butler query language can search by sky position and time: the detector whose footprint (visit_detector_region.region) contains the observed position, in the visit whose timespan contains the observation time. Values are passed through bind rather than pasted into the string. Querying for preliminary_visit_image datasets (rather than bare data IDs) returns only images present in the release.
Define a function (find_image) to return the instrument, detector, visit, band, day_obs, and physical_filter for the image for a single detection present in the release. The detector regions are slightly padded and near a chip edge two detectors can match, so keep the one whose image really contains the position.
Note: The
find_imagefunction works for any position and time, including a prediction from your own orbit.
def find_image(row):
refs = butler.query_datasets(
"preliminary_visit_image",
where="instrument = 'LSSTCam' AND visit_detector_region.region OVERLAPS POINT(ra, dec) "
"AND visit.timespan OVERLAPS t",
bind=dict(ra=float(row["ra"]), dec=float(row["dec"]),
t=Time(row["obsTime"].rstrip("Z"), scale="utc")),
with_dimension_records=True,
)
sky = geom.SpherePoint(float(row["ra"]), float(row["dec"]), geom.degrees)
on = [r for r in refs if geom.Box2D(butler.get(r.makeComponentRef("bbox"))).contains(
butler.get(r.makeComponentRef("wcs")).skyToPixel(sky))]
assert len(on) == 1, on
return on[0].dataId
Define the half-size in pixels (0.2"/pixel: a 20" stamp) for creating the cutout generation bounding box.
HALF = 50
A full LSSTCam detector image is 4k×4k pixels.
Define a function (cutout) to read a stamp of the full image that reads the image's WCS (a component of the dataset, read without the pixels), converting the sky position to pixels, and then passing a bounding box as a read parameter so that only that part of the file is read. Note that near a detector edge, the stamp is smaller.
def cutout(row, data_id):
wcs = butler.get("preliminary_visit_image.wcs", data_id)
full = butler.get("preliminary_visit_image.bbox", data_id)
xy = wcs.skyToPixel(geom.SpherePoint(float(row["ra"]), float(row["dec"]), geom.degrees))
bbox = geom.Box2I(geom.Point2I(int(xy.x) - HALF, int(xy.y) - HALF),
geom.Extent2I(2 * HALF + 1, 2 * HALF + 1))
bbox.clip(full)
return butler.get("preliminary_visit_image", data_id, parameters={"bbox": bbox}), xy
1.2.1. Parameters for source detection in a cutout¶
To mark everything visible in a stamp, run the pipelines' own detection on it. SourceDetectionTask convolves the image with its PSF and finds peaks above 5-sigma; every pixel it needs (image, variance, PSF) is in the stamp. The peak closest to the MPC position is 2026 ED138.
First, quiet the tasks' INFO messages.
logging.getLogger("lsst").setLevel(logging.WARNING)
Set up the needed configurations for detecting sources in the cutouts, including setting the 5-sigma threshold and not re-estimating the background since the images are already background-subtracted.
config = SourceDetectionTask.ConfigClass()
config.thresholdValue = 5.0
config.reEstimateBackground = False
Retrieve the afwTable schema needed for source detection.
schema = afwTable.SourceTable.makeMinimalSchema()
Define the detection parameters using the above configurations and afwTable schema using SourceDetectionTask to convolve the image with its PSF to find peaks above 5-sigma.
detection = SourceDetectionTask(config=config, schema=schema)
Define a function to retrieve the x, y pixel coordinates of every detected peak in detector pixels.
def detect(stamp):
found = detection.run(afwTable.SourceTable.make(schema), stamp.clone(), doSmooth=True)
return (
np.array([
(p.getFx(), p.getFy())
for src in found.sources
for p in src.getFootprint().getPeaks()
]).reshape(-1, 2)
)
Define a function that marks the pipelines' own detection on an image.
def show(ax, stamp, xy, title):
img = stamp.image.array
vmin, vmax = ZScaleInterval().get_limits(img)
x0, y0 = stamp.getXY0()
ext = (x0 - 0.5, x0 + img.shape[1] - 0.5, y0 - 0.5, y0 + img.shape[0] - 0.5)
ax.imshow(img, origin="lower", cmap="gray", vmin=vmin, vmax=vmax, extent=ext)
for x, y in detect(stamp):
if np.hypot(x - xy.x, y - xy.y) > 5:
ax.add_patch(plt.Circle((x, y), 5, fill=False, color="deepskyblue", lw=0.8))
ax.add_patch(plt.Circle((xy.x, xy.y), 7, fill=False, color="red", lw=1.5))
ax.set_xlim(ext[:2])
ax.set_ylim(ext[2:])
ax.set_title(title, fontsize=8)
ax.set_xticks([])
ax.set_yticks([])
2. Contents of the data repository¶
Print each dataset type along with the number of datasets each dataset type contains in the release repository.
The preliminary_visit_image dataset type are Prompt Processing's calibrated single-visit images (one per visit and detector); the single_visit_star_* dataset type are source catalogs of the same detectors from three processings.
for dt in sorted(butler.registry.queryDatasetTypes(), key=lambda d: d.name):
n = len(butler.query_datasets(dt, limit=None, explain=False))
if n:
print(f"{dt.name:48s} {n:5d} {dt.storageClass_name}")
preliminary_visit_image 242 ExposureF preliminary_visit_image_background 213 Background preliminary_visit_image_reprocessed_background 33 Background raw 48 Exposure singleFrameDetectAndMeasure_config 1 Config single_visit_star_detector 434 ArrowAstropy single_visit_star_footprints 52 SourceCatalog single_visit_star_reprocessed_footprints 60 SourceCatalog single_visit_star_reprocessed_unstandardized 256 ArrowAstropy the_monster_20250219 25 SimpleCatalog
3. Read in MPC submission files¶
The file submitted to the Minor Planet Center (MPC) that contains the reported observed astrometry and photometry for the object is a pipe-separated table; # and ! lines are its header.
Read in the file, strip the file of spaces padding each column, and print the first five rows of the rows.
obs = Table.read(f"{RELEASE}/ades/2026_ED138.psv",
format="ascii.csv", delimiter="|", comment="[#!]")
for c in obs.colnames:
obs.rename_column(c, c.strip())
if obs[c.strip()].dtype.kind == "U":
obs[c.strip()] = np.char.strip(obs[c.strip()])
obs["ra", "dec", "obsTime", "mag", "band"][:5]
| ra | dec | obsTime | mag | band |
|---|---|---|---|---|
| float64 | float64 | str24 | float64 | str2 |
| 274.7906291 | -42.4543647 | 2026-03-04T09:33:14.740Z | 19.319 | Ly |
| 274.790647 | -42.4543812 | 2026-03-04T09:35:07.430Z | 19.286 | Ly |
| 275.0026189 | -42.6096994 | 2026-03-31T08:28:06.437Z | 19.414 | Lz |
| 275.0026441 | -42.6098348 | 2026-03-31T09:00:12.636Z | 19.411 | Lz |
| 274.9631508 | -42.7055538 | 2026-04-16T07:56:43.520Z | 21.941 | Lu |
4. Find the image metadata of a single observation¶
Use the find_image function to return the instrument, detector, visit, band, day_obs, and physical_filter for the image for a single detection present in the release. In this case, use row 7 for the detection on 2 May 2026.
row = obs[6]
data_id = find_image(row)
Option to print the image metadata.
# data_id
5. Generate a cutout¶
Use the cutout function to generate a cutout for the image metadata retrieved above.
stamp, xy = cutout(row, data_id)
Option to print the bounding box minimum and maximum coordinates, the filter, and the image timestamp.
# print(stamp.getBBox(), stamp.getFilter().bandLabel, stamp.visitInfo.date)
6. Explore the star catalog¶
Get the table of stars observed on the detector that is used for nightly validation processing (single_visit_star_detector). Importantly for the crowded field in which 2026 ED138 is detected, this table excluded blended stars.
stars = butler.get("single_visit_star_detector", data_id)
Option to print the star catalog.
# stars
Grab the bounding box used for the image stamp above.
b = stamp.getBBox()
Collect the stars that fit within the bounding box of the image stamp.
in_stamp = (
(stars["x"] >= b.getMinX())
& (stars["x"] <= b.getMaxX())
& (stars["y"] >= b.getMinY())
& (stars["y"] <= b.getMaxY())
)
Print the number of stars in the catalog observed on the detector along with the number of stars that fall within the stamp.
print(f"{len(stars)} catalog stars on the detector, {in_stamp.sum()} in the stamp")
3432 catalog stars on the detector, 3 in the stamp
Print the x, y pixel coordinates, psfFlux, psfFluxErr, and extendedness parameter for the stars in the stamp.
stars[in_stamp]["x", "y", "psfFlux", "psfFluxErr", "extendedness"]
| x | y | psfFlux | psfFluxErr | extendedness |
|---|---|---|---|---|
| float64 | float64 | float64 | float64 | float64 |
| 1410.3287402066105 | 1622.9628675095576 | 55099.46604129995 | 616.8429666843782 | 0.0 |
| 1410.339836207675 | 1622.9707983328535 | 54791.4527601585 | 616.85099577 | 0.0 |
| 1401.5086690648413 | 1614.9187611493337 | 17418.956913726914 | 575.0295035981061 | 0.0 |
7. Single image cutout¶
Display the cutout, using the show function to mark the pipelines' detections on the retrieved cutout. 2026 ED138 is circled in red at its MPC position.
fig, ax = plt.subplots(figsize=(4, 4))
show(ax, stamp, xy, f"{row['obsTime'][:16]} {stamp.getFilter().bandLabel}")
Figure 1: Single image cutout of 2026 ED138 marked in red at the position submitted to the MPC. In blue are additional sources detected in the stamp.
8. Cutouts for all 30 observations¶
Define the number of columns and rows in which to display the images.
ncol = 6
nrow = int(np.ceil(len(obs) / ncol))
Display the cutouts for all 30 observations of 2026 ED138, marking the positions submitted to the MPC.
fig, axes = plt.subplots(nrow, ncol, figsize=(2 * ncol, 2.1 * nrow))
for row, ax in zip(obs, axes.flat):
stamp, xy = cutout(row, find_image(row))
show(ax, stamp, xy, f"{row['obsTime'][:10]} {stamp.getFilter().bandLabel} {row['mag']}")
for ax in axes.flat[len(obs):]:
ax.axis("off")
title = "2026 ED138 (red) in its 30 MPC-reported LSSTCam detections; " + \
"other detected sources in blue"
fig.suptitle(title, fontsize=11)
fig.tight_layout()
Figure 2: All 30 image cutouts in the release of 2026 ED138 marked in red at the position submitted to the MPC. In blue are additional sources detected in the stamp. As noted above, when near a detector edge, the cutout is smaller.