DiaSource cutouts and lightcurves#
Two Tasks render per-diaSource PNG images from a butler repository and an APDB:
PlotImageSubtractionCutoutsTaskA 3-panel template/science/difference cutout per diaSource, optionally annotated with catalog metadata and optionally accompanied by a Zooniverse upload manifest. See its task page for the annotation layout and example images.
PlotDiaSourceLightcurveTaskA subclass that adds a lightcurve panel below the cutouts, showing the whole diaSource history of the associated diaObject.
Both write {diaSourceId}.png under output_path/images/, and both are available
as command-line scripts:
plotImageSubtractionCutouts and
plotDiaSourceLightcurve.
From the command line#
The scripts connect to an APDB, page through its diaSources, and write cutouts.
Exactly one of --sqlitefile or --namespace is required, to select a SQLite
file or a Postgres namespace:
plotImageSubtractionCutouts --sqlitefile association.db \
--collections u/me/my_ap_run -j 8 --limit 800 \
/repo/main ./cutouts
--limit is the number of sources to process, or — with --all — the page size
used to chunk the whole table.
-j/--jobs sets the multiprocessing pool size; keep --limit at least 100×
-j so each worker gets a useful batch.
--reliabilityMin/--reliabilityMax restrict the selection to a reliability
range, which is how you pull “the ones the classifier was unsure about”.
-C/--configFile loads a PlotImageSubtractionCutoutsConfig.
The lightcurve script takes the same database, collection, and reliability arguments
but no -j: it runs single-process, because it caches lightcurves per diaObject and
multiprocessing would defeat the cache.
Passing njobs to the Task’s run or write_images logs a warning and is
ignored.
From Python#
Construct the Task with an output_path and call
run with a pandas.DataFrame of diaSources and a
butler created with the collections to read images from.
The DataFrame must have at least ra, dec, diaSourceId, detector,
visit, and instrument columns:
import lsst.daf.butler as dafButler
from lsst.analysis.ap import (ApdbSqliteQuery,
PlotImageSubtractionCutoutsConfig,
PlotImageSubtractionCutoutsTask)
butler = dafButler.Butler("/repo/main", collections="u/me/my_ap_run")
query = ApdbSqliteQuery("association.db")
config = PlotImageSubtractionCutoutsConfig()
config.sizes = [30, 60] # two cutout scales, stacked
config.add_metadata = True
task = PlotImageSubtractionCutoutsTask(config=config, output_path="./cutouts")
sources = query.load_sources(limit=200)
ids = task.run(sources, butler, njobs=4)
run returns the diaSourceIds it succeeded on;
sources whose images could not be loaded are logged and dropped rather than raising, so
one missing dataset does not abort a long run.
When running single-process (njobs=0, the default), diaSources whose PNG already
exists are skipped, which makes a re-run after a partial failure cheap; the
multiprocessing path re-renders them.
It splits into write_images (the PNGs) and
write_manifest (the Zooniverse CSV); call the
former alone if you only want images.
generate_image produces a single cutout as an
in-memory io.BytesIO, which is what to use to display one cutout in a notebook
without touching disk.
Configuration#
The fields you are most likely to set (see the full list):
sizesList of cutout widths in pixels; more than one produces a row of panels per size.
use_footprintSize each cutout from the diaSource’s footprint bounding box instead of
sizes. This reads the footprint-bearing detection catalog, which must be sorted by id.add_metadataAnnotate the image with coordinates, fluxes, and flags, coloring flagged measurements red and matching
Displaymask-plane colors.science_image_type,diff_image_typeDataset-type overrides for older processings, whose science image was
calexporinitial_pvi.url_rootBase URL the images will be served from. Setting it is what causes
manifest.csvto be written; leaving it unset skips the manifest entirely.chunk_sizeFiles per subdirectory (must be a power of 10), so a large run does not put millions of PNGs in one directory.
save_as_numpyAlso write the raw cutout arrays under
output_path/numpy/, for when you want to measure the pixels rather than look at them.
CutoutPath implements that chunking and is useful on its own for finding the images
a previous run wrote:
from lsst.analysis.ap import CutoutPath
path = CutoutPath("./cutouts", chunk_size=10000)
path(diaSourceId, f"{diaSourceId}.png") # full path
path.exists(diaSourceId, f"{diaSourceId}.png")
Adding a lightcurve panel#
PlotDiaSourceLightcurveTask takes an extra apdb_query argument — the DbQuery
it uses to load the diaObject’s history — and the input DataFrame needs two more
columns, diaObjectId and midpointMjdTai:
from lsst.analysis.ap import (PlotDiaSourceLightcurveConfig,
PlotDiaSourceLightcurveTask)
config = PlotDiaSourceLightcurveConfig()
config.lightcurve_height = 3.0
task = PlotDiaSourceLightcurveTask(config=config,
output_path="./lightcurves",
apdb_query=query)
task.run(query.load_sources(limit=50), butler)
The panel plots the diaObject’s diaSources with one marker
(lightcurve_marker_source, default o) and overlays diaForcedSource
measurements for visits that have a forced measurement but no diaSource with a
different marker (lightcurve_marker_forced_only, default v) — so a gap in the
detections is visibly distinguishable from a gap in the coverage.
With highlight_current_source (the default) the diaSource being cut out is marked
with a vertical line and an open ring, tying the panel to the images above it.
lightcurve_exclude_flagged defaults to False so the panel’s row count matches a
direct APDB query; set it True to drop diaSources hitting the bad-flag list.
DiaForcedSources are always loaded unfiltered.
Unassociated diaSources have a NaN diaObjectId; those render an empty placeholder
panel rather than failing, as does a failed lightcurve query.
PlotDiaSourceLightcurveConfig inherits every field of
PlotImageSubtractionCutoutsConfig, so sizes, add_metadata and the rest apply
unchanged.