Introduction to the LSST deblender data products¶

No description has been provided for this image
Contact author(s): Christina Williams
Last verified to run: 2024-08-19
LSST Science Pipelines version: Weekly 2024_32
Container Size: medium
Targeted learning level: beginner

Description: This tutorial introduces the data products created by the LSST multiband deblender (Scarlet; Melchior et al. 2018) and illustrates some simple use cases for them.

Skills: Use of the catalog data products related to deblending objects.

LSST Data Products: images (deepCoadd, skyMap, MaskedImage) and catalogs (objectTable)

Packages: lsst.afw.image, lsst.afw.detection, lsst.rsp, lsst.daf.butler, lsst.geom

Credit: Developed by Christina Willliams in collaboration with Melissa Graham and the Rubin Community Science Team for DP0.2. Influenced by Stack Club notebooks demonstrating how to run the LSST deblender (Scarlet; Melchior et al. 2018) by Fred Moolekamp and Alex Drlica-Wagner.

Get Support: Find DP0-related documentation and resources at dp0.lsst.io. Questions are welcome as new topics in the Support - Data Preview 0 Category of the Rubin Community Forum. Rubin staff will respond to all questions posted there.

1. Introduction¶

This notebook is an introduction to the data products that are associated with the deblending process.

In order to build its source catalog and measure photometry of objects in images, the LSST Science Pipeline uses the multi-wavelength deblending algorithm Scarlet (Melchior et al. 2018) to identify independent detected components within source blends and separate the flux from independent objects.

A "parent" source is identified as a region of connected pixels above a given signal-to-noise ratio (S/N) in the deepCoadd image. These regions are called footprints. Each footprint may have one or more peaks, and it is these peaks that the deblender will use to infer the number and positions of objects blended in each footprint. The deblended sub-peaks are referred to as "children" of the parent in the properties saved by the deblender in the Object table.

The Scarlet deblender separates the flux among components by modeling the flux and shape of each source making up the blend.

This tutorial will identify a heavily blended parent object with many children and explore how to interpret the deblender-related measurements in the Object table. It will also demonstrate how to use deblender-related columns in the Object table to identify unique deblended child sources, and provide an example of how to access metadata that is available for each deblended source (e.g. the deblended footprints and pixel-weight maps).

Additional resources¶

An introduction to all of the available deblending flags, and the processes that produce the deblender data products discussed in this tutorial can be found in this overview of the deblending flags.

A general discussion of blending impacts to LSST can be found in this article titled "The challenge of blending in large sky surveys" by Melchior et al. 2021.

Some more in-depth presentations on the deblending implementation in the LSST Science Pipelines are available from recorded talks during the Rubin Project and Community Workshop 2022's session on "Deblending Plans and Challenges".

1.1. Package Imports¶

The matplotlib (and especially sublibrary matplotlib.pyplot), numpy, and astropy libraries are widely used Python libraries for plotting, scientific computing, and astronomical data analysis.

The lsst.rsp package provides access to the Table Access Protocol (TAP) service for queries to the DP0 catalogs.

The lsst.afw.image and lsst.afw.detection packages provide access to some of the extended products created by the deblender pipeline.

The lsst.afw.display library provides access to image visualization routines and the lsst.daf.butler library is used to access data products via the butler.

In [1]:
import numpy as np
import matplotlib.pyplot as plt
import lsst.daf.butler as Butler
from lsst.rsp import get_tap_service
import lsst.geom as geom
import lsst.afw.display as afwDisplay
from lsst.afw.image import MaskedImage

1.2. Define functions and parameters¶

The following two functions enable the visualization of deblended products:

  1. cutout_coadd will make an image cutout (see DP0.2 tutorial notebook 03a on image display).
  2. heavyFootprint2Image will help us grab the "footprint" of deblended objects for further analysis.
In [2]:
def cutout_coadd(butler, ra, dec, band='r', datasetType='deepCoadd',
                 skymap=None, cutoutSideLength=51, **kwargs):
    """
    Produce a cutout from a coadd at the given ra, dec position.

    Adapted from DC2 tutorial notebook by Michael Wood-Vasey.

    Parameters
    ----------
    butler: lsst.daf.persistence.Butler
        Helper object providing access to a data repository
    ra: float
        Right ascension of the center of the cutout, in degrees
    dec: float
        Declination of the center of the cutout, in degrees
    band: string
        Filter of the image to load
    datasetType: string ['deepCoadd']
        Which type of coadd to load.  Doesn't support 'calexp'
    skymap: lsst.afw.skyMap.SkyMap [optional]
        Pass in to avoid the Butler read.  Useful if you have lots of them.
    cutoutSideLength: float [optional]
        Size of the cutout region in pixels.

    Returns
    -------
    MaskedImage
    """
    radec = geom.SpherePoint(ra, dec, geom.degrees)
    cutoutSize = geom.ExtentI(cutoutSideLength, cutoutSideLength)

    if skymap is None:
        skymap = butler.get("skyMap")

    tractInfo = skymap.findTract(radec)
    patchInfo = tractInfo.findPatch(radec)
    xy = geom.PointI(tractInfo.getWcs().skyToPixel(radec))
    bbox = geom.BoxI(xy - cutoutSize // 2, cutoutSize)
    print("bbox = ", bbox, "xy = ", xy, "cutoutSize = ", cutoutSize)
    patch = tractInfo.getSequentialPatchIndex(patchInfo)

    coaddId = {'tract': tractInfo.getId(), 'patch': patch, 'band': band}
    parameters = {'bbox': bbox}

    cutout_image = butler.get(datasetType, parameters=parameters,
                              dataId=coaddId)

    return cutout_image
In [3]:
def heavyFootprint2Image(heavy, fill=np.nan, bbox=None, imageType=MaskedImage):
    """
    Create an image of a HeavyFootprint
    Written by Fred Moolekamp

    Parameters
    ----------
    heavy : `HeavyFootprint`
        The HeavyFootprint to insert into the image
    fill: number
        Number to fill the pixels in the image that are not
        contained in `heavy`.
    bbox : `Box2I`
        Bounding box of the output image.
    imageType : `type`
        This should be either a `MaskedImage` or `Image` and describes
        the type of the output image.

    Returns
    -------
    image : `lsst.afw.image.MaskedImage` or `lsst.afw.image.Image`
        An image defined by `bbox` and padded with `fill` that
        contains the projected flux in `heavy`.
    """
    if bbox is None:
        bbox = heavy.getBBox()
    image = imageType(bbox, dtype=heavy.getImageArray().dtype)
    image.set(fill)
    heavy.insert(image)
    return image

Define parameters for plotting.

In [4]:
afwDisplay.setDefaultBackend('matplotlib')
plt.style.use('tableau-colorblind10')

params = {'axes.labelsize': 28,
          'font.size': 24,
          'legend.fontsize': 14,
          'xtick.major.width': 3,
          'xtick.minor.width': 2,
          'xtick.major.size': 12,
          'xtick.minor.size': 6,
          'xtick.direction': 'in',
          'xtick.top': True,
          'lines.linewidth': 3,
          'axes.linewidth': 3,
          'axes.labelweight': 3,
          'axes.titleweight': 3,
          'ytick.major.width': 3,
          'ytick.minor.width': 2,
          'ytick.major.size': 12,
          'ytick.minor.size': 6,
          'ytick.direction': 'in',
          'ytick.right': True,
          'figure.figsize': [8, 8],
          'figure.facecolor': 'White'}
plt.rcParams.update(params)

Start the TAP service.

In [5]:
service = get_tap_service("tap")
assert service is not None

Instantiate the butler.

In [6]:
butler = Butler.Butler('dp02', collections='2.2i/runs/DP0.2')

2. The deblender data products¶

The object and source catalogs contain a number of columns that can be used to identify and characterize blended sources (parents) that have been deblended into child sources, both of which are ultimately stored in the catalogs along with isolated single sources that do not require deblending. These columns help the user identify a unique sample and avoid returning duplicate objects in queries.

In this tutorial we will focus on deblending data products for Objects detected and measured in the deepCoadd images.

The following describes boolean flags that are set by the deblender, and some measurements or properties of deblended sources that can be used to characterize (de)blended sources.

Note: This tutorial uses catalog data that are available via TAP and/or the butler. In the DP0-era, some deblending data products are available only via butler-based catalogs, and cannot be accessed by TAP and are not listed in the DP0.2 Schema Browser.

Available in the TAP Object table and in the butler-based objectTable:

detect_isPrimary (boolean): True if source has no children, is in inner region of a coadd patch, and is in the inner region of a coadd tract (i.e., is a unique object in the catalog).
Note that an object where detect_isPrimary = True can still be a deblended child, but itself was not a parent that was separated by the deblender.

Warning: to avoid returning duplicates use detect_isPrimary = True for all catalog queries. (Duplicates can be caused by including both the parent blend and the nearest child source, or by including the overlapping area outside the "inner region" of a patch or tract.

detect_fromBlend (boolean): If True, this source is deblended from a parent with more than one child.

detect_isIsolated (boolean): If True, this source was not part of a blend (i.e., the source has a single peak, deblend_nPeaks = 1, or its parent only had a single peak, deblend_parentNPeaks = 1).

detect_isDeblendedSource (boolean): True if source has no children, is in the inner regions of its coadd patch and tract (will be unique) and is either an unblended isolated source or a deblended child from a parent.

detect_isDeblendedModelSource (boolean): True if the source has no children and is a deblended child (but this flag is false for unblended isolated sources); similar to detect_isDeblendedSource.

deblend_skipped (boolean): If True, the deblender skipped this source and did not attempt to deblend.

parentObjectId (long): Unique ID of parent source.

deblend_nChild (int): Number of children this object has (defaults to 0). Differs from deblend_nPeaks in that deblend_nChild excludes both isolated sources and child peaks that were culled during deblending.

footprintArea (int): Number of pixels in the source's detection "footprint". The footprint itself is a boolean mask of pixels in the input image that were detected as part of the parent blend. This corresponds to the footprint for the "reference band".

refBand (char): The filter that the deblender identified the "best" location for the centroid of the peak flux based on the multi-wavelength deblending routine (referred to as the "reference filter").

<f>_blendedness (dbl): For each filter (f = u, g, r, i, z, or y), a measure of how much the flux is affected by neighbors, (1 - child_flux/parent_flux). Operates on the absolute value of the pixels to try to obtain a de-noised value. See section 4.9.11 of Bosch et al. 2018, PASJ, 70, S5 for details.

Available only in the butler-based objectTable:

deblend_nPeaks (int): Number of peaks contained in the parent blend's footprint (is 1 for isolated sources).

deblend_parentNPeaks (int): Number of peaks of the deblended child's parent.

2.1. Find parents with many children¶

Use the TAP service to find a bunch of parent blended objects.

As with all explorative TAP queries searching for examples, it is essential to first start with a small area while developing queries (to confirm the query is good), and then iteratively increase the area as needed (because the full query could take hours without any spatial constraint on the search). This is because the tables are indexed by coordinate (RA, Dec) and queries with constraints only on measurement columns (such as deblending-related parameters) are inefficient. In other words, Qserv (the backend hosting the TAP-accessible tables) is "spatially sharded". For more information about efficient TAP queries, see tutorial 02.

Search in the center of the DC2 simulation for wide-area (footprintArea > 20000, or about 30"x30") parent objects with many children (deblend_nChild > 20) that are not point-source like (refExtendedness = 1).

In [7]:
ra = 55.064
dec = -29.783
query = "SELECT objectId, coord_ra, coord_dec, x, y, tract, patch, " + \
        "refExtendedness, detect_isPrimary, detect_fromBlend, " + \
        "deblend_nChild, footprintArea " + \
        "FROM dp02_dc2_catalogs.Object " + \
        "WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), " + \
        "CIRCLE('ICRS', " + str(ra) + ", " + str(dec) + ", 0.2)) = 1 " + \
        "AND deblend_nChild > 20 AND footprintArea > 20000 " + \
        "AND refExtendedness = 1"
print(query)
SELECT objectId, coord_ra, coord_dec, x, y, tract, patch, refExtendedness, detect_isPrimary, detect_fromBlend, deblend_nChild, footprintArea FROM dp02_dc2_catalogs.Object WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), CIRCLE('ICRS', 55.064, -29.783, 0.2)) = 1 AND deblend_nChild > 20 AND footprintArea > 20000 AND refExtendedness = 1
In [8]:
job = service.submit_job(query)
job.run()
job.wait(phases=['COMPLETED', 'ERROR'])
print('Job phase is', job.phase)
Job phase is COMPLETED
In [9]:
parents = job.fetch_result().to_table()
In [10]:
parents
Out[10]:
Table length=9
objectIdcoord_racoord_decxytractpatchrefExtendednessdetect_isPrimarydetect_fromBlenddeblend_nChildfootprintArea
degdegpixpixpix
int64float64float64float64float64int64int64float64boolboolint32int32
209042669109982832055.1771286-29.61040919271.42257523152.3404409485041.0FalseFalse4824180
209043548719284984454.9815469-29.611300222332.34870313124.8217907485051.0FalseFalse6230827
199940472050601822754.9375278-29.80309268682.211737926461.71139984638441.0FalseFalse5324626
199940472050602028954.8683502-29.76557939761.19193727139.92452124638441.0FalseFalse4821010
199940472050601400654.9791041-29.89252588038.202184724849.6543654638441.0FalseFalse4825320
199939592441299161755.1751359-29.89365654978.863339624816.52554514638431.0FalseFalse6647579
209044428328586525854.8683501-29.765579624088.8219365338.3070552485061.0FalseFalse4821108
199939592441299089155.2238077-29.91644224221.517272124402.34748854638431.0FalseFalse5021950
209042669109982490955.2106826-29.674730518743.31327041995.8530579485041.0FalseFalse7932435

Use the cutout_coadd function to obtain a cutout image of one of these parents (use parent index pi=1). Make the side length 3x the square root of the footprintArea.

In [11]:
pi = 1
cutout = cutout_coadd(butler, parents['coord_ra'][pi],
                      parents['coord_dec'][pi],
                      band='i', datasetType='deepCoadd',
                      cutoutSideLength=3.0
                      * np.sqrt(float(parents['footprintArea'][pi])))
bbox =  (minimum=(22069, 2862), maximum=(22594, 3387)) xy =  (22332, 3125) cutoutSize =  (526, 526)

Use TAP to retrieve all children for the selected parent.

In [12]:
query = "SELECT objectId, coord_ra, coord_dec, x, y " + \
        "FROM dp02_dc2_catalogs.Object " + \
        "WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), " + \
        "CIRCLE('ICRS', " + str(ra) + ", " + str(dec) + ", 0.3)) = 1 " + \
        "AND parentObjectId = " + str(parents['objectId'][pi]) + " " + \
        "AND detect_isPrimary = 1"
print(query)
SELECT objectId, coord_ra, coord_dec, x, y FROM dp02_dc2_catalogs.Object WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), CIRCLE('ICRS', 55.064, -29.783, 0.3)) = 1 AND parentObjectId = 2090435487192849844 AND detect_isPrimary = 1
In [13]:
job = service.submit_job(query)
job.run()
job.wait(phases=['COMPLETED', 'ERROR'])
print('Job phase is', job.phase)
Job phase is COMPLETED
In [14]:
children = job.fetch_result().to_table()
In [15]:
# children

Display the deepCoadd cutout made for this parent, with a blue cross for the parent and red circles for its children.

In [16]:
fig = plt.figure(figsize=(6, 6))
display = afwDisplay.Display(frame=fig)
display.scale('asinh', 'zscale')
display.mtv(cutout.image)
with display.Buffering():
    display.dot('+', parents['x'][pi], parents['y'][pi], ctype=afwDisplay.BLUE)
    for ci in range(len(children)):
        display.dot('o', children['x'][ci], children['y'][ci],
                    size=10, ctype=afwDisplay.RED)
plt.gca().axis('off')
display.show_colorbar(False)
plt.show()
No description has been provided for this image

Figure 1: The cutout image displayed in greyscale, with a blue + marking the parent object and red circles marking the deblended children. In this case, the blended object looks like a rich galaxy cluster.

Note: Performance of the deblender is still being optimized, and results like this within DP0 are not necessarily representative of future results.

In [17]:
del parents, children, cutout

2.2. Find a parent at a given location¶

In the case where the user wants to explore the (de)blended objects at a specific location, the process is similar to the one above except here, no constraints are made except spatial constraints.

In this case our search area is limited to a circle of radius 0.003 degrees, or about 10 arcseconds.

In [18]:
ra = 50.1089143
dec = -44.4812763
query = "SELECT objectId, coord_ra, coord_dec, x, y, tract, patch, " + \
        "detect_isPrimary, deblend_nChild, deblend_skipped, " + \
        "detect_fromBlend, detect_isDeblendedModelSource, " +\
        "detect_isDeblendedSource, detect_isIsolated, parentObjectId, " + \
        "footprintArea, z_blendedness " + \
        "FROM dp02_dc2_catalogs.Object " + \
        "WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), " + \
        "CIRCLE('ICRS', " + str(ra) + ", " + str(dec) + ", 0.003)) = 1 "
print(query)
SELECT objectId, coord_ra, coord_dec, x, y, tract, patch, detect_isPrimary, deblend_nChild, deblend_skipped, detect_fromBlend, detect_isDeblendedModelSource, detect_isDeblendedSource, detect_isIsolated, parentObjectId, footprintArea, z_blendedness FROM dp02_dc2_catalogs.Object WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), CIRCLE('ICRS', 50.1089143, -44.4812763, 0.003)) = 1 
In [19]:
job = service.submit_job(query)
job.run()
job.wait(phases=['COMPLETED', 'ERROR'])
print('Job phase is', job.phase)
Job phase is COMPLETED
In [20]:
table = job.fetch_result().to_table()
In [21]:
table
Out[21]:
Table length=22
objectIdcoord_racoord_decxytractpatchdetect_isPrimarydeblend_nChilddeblend_skippeddetect_fromBlenddetect_isDeblendedModelSourcedetect_isDeblendedSourcedetect_isIsolatedparentObjectIdfootprintAreaz_blendedness
degdegpixpixpix
int64float64float64float64float64int64int64boolint32boolboolboolboolboolint64int32float64
124867577324661054250.1089143-44.481276323484.49695353210.155201428975False13FalseFalseFalseFalseFalse034350.0
124867577324661055350.1057834-44.480319923524.86433993227.007505428975True1FalseFalseFalseTrueTrue058--
124867577324661450650.1112455-44.483064123454.2676893178.241811728975True1FalseFalseFalseTrueTrue0146--
124867577324664673150.1112542-44.483059523454.1567253178.324340128975False0FalseFalseTrueFalseTrue12486757732466145062101.0
124867577324664806650.1068549-44.479212823511.27938233247.063537328975False0FalseFalseTrueFalseTrue12486757732466158702100.6840147
124867577324664804250.1089035-44.482914823484.37130653180.657662728975False0FalseFalseTrueFalseTrue12486757732466158501210.8361302
124867577324664073450.1058255-44.480332323524.32115683226.789541428975False0FalseFalseTrueFalseTrue1248675773246610553900.8621852
124867577324664070650.1066973-44.481509123512.93433893205.707249928975True0FalseTrueTrueTrueFalse124867577324661054231900.4453317
124867577324664070550.1090308-44.479283223483.32000833246.048711928975True0FalseTrueTrueTrueFalse124867577324661054236580.1195687
...................................................
124867577324664071350.1093602-44.480325523478.92198133227.323819928975True0FalseTrueTrueTrueFalse124867577324661054242701.0
124867577324664071250.1103673-44.481994723465.71870793197.391648828975True0FalseTrueTrueTrueFalse124867577324661054232351.0
124867577324664071150.1074871-44.482466823502.63552373188.557209128975True0FalseTrueTrueTrueFalse12486757732466105422094--
124867577324664071050.1080708-44.482699423495.10127533184.43856428975True0FalseTrueTrueTrueFalse124867577324661054225850.6481874
124867577324664070950.1109064-44.480131823459.09317833230.988640528975True0FalseTrueTrueTrueFalse124867577324661054233600.6900869
124867577324664070850.1086507-44.480078223488.07512763231.692096728975True0FalseTrueTrueTrueFalse124867577324661054247610.3823447
124867577324664070750.1112017-44.479511723455.40066613242.185687528975True0FalseTrueTrueTrueFalse124867577324661054219510.391979
124867577324661587050.1068648-44.479212823511.15309793247.063948428975True1FalseFalseFalseTrueTrue0152--
124867577324661585050.1089686-44.482867123483.54282253181.523796928975True1FalseFalseFalseTrueTrue081--

The first row in the results table is a parent source with 13 deblended children (it has detect_isPrimary = False, and deblend_nChild = 13).

Double check that all of the children are included in the table by finding out how many rows have a parentObjectId matching the first row's objectId.

In [22]:
sel_objid = table['objectId'][0]
whchild = np.where(table['parentObjectId'] == sel_objid)[0]
print(len(whchild), "child objects deblended from parentObjectId", sel_objid)
13 child objects deblended from parentObjectId 1248675773246610542

Up until now, the TAP service has been used to access catalog data.

Below, all objects in the same tract and patch as the 13-child parent are obtained via the butler.

In [23]:
tract = table['tract'][0]
patch = table['patch'][0]
dataId = {'tract': tract, 'patch': patch, 'band': 'i'}
obj = butler.get('objectTable', dataId)
print("Retrieved catalog of ", len(obj), " objects.")
Retrieved catalog of  32101  objects.

Print the parameters only for the objects that are children of the parent identified above.

In [24]:
children = obj[obj.parentObjectId == sel_objid]
children[['parentObjectId', 'x', 'y', 'coord_ra', 'coord_dec']]
Out[24]:
column parentObjectId x y coord_ra coord_dec
objectId
1248675773246640703 1248675773246610542 23484.493982 3210.159524 50.108914 -44.481276
1248675773246640704 1248675773246610542 23466.945145 3233.628800 50.110297 -44.479981
1248675773246640705 1248675773246610542 23483.320008 3246.048712 50.109031 -44.479283
1248675773246640706 1248675773246610542 23512.934339 3205.707250 50.106697 -44.481509
1248675773246640707 1248675773246610542 23455.400666 3242.185688 50.111202 -44.479512
1248675773246640708 1248675773246610542 23488.075128 3231.692097 50.108651 -44.480078
1248675773246640709 1248675773246610542 23459.093178 3230.988641 50.110906 -44.480132
1248675773246640710 1248675773246610542 23495.101275 3184.438564 50.108071 -44.482699
1248675773246640711 1248675773246610542 23502.635524 3188.557209 50.107487 -44.482467
1248675773246640712 1248675773246610542 23465.718708 3197.391649 50.110367 -44.481995
1248675773246640713 1248675773246610542 23478.921981 3227.323820 50.109360 -44.480325
1248675773246640714 1248675773246610542 23503.206785 3201.591833 50.107452 -44.481743
1248675773246640715 1248675773246610542 23463.005462 3244.569998 50.110611 -44.479375

An alternative way of printing the children array defined above.

In [25]:
# for i in range(len(children)):
#     print(children.iloc[i].parentObjectId, children.index[i],
#           children.iloc[i].x, children.iloc[i].y,
#           children.iloc[i].coord_ra, children.iloc[i].coord_dec)

Another alternative for defining separate arrays containing the data from children, and printing them with a user-specified format.

In [26]:
# obj_parentObjectId = np.asarray(obj.parentObjectId, dtype='int')
# obj_objectId = np.asarray(obj.index, dtype='int')
# obj_x = np.asarray(obj.x, dtype='float')
# obj_y = np.asarray(obj.y, dtype='float')
# obj_ra = np.asarray(obj.coord_ra, dtype='float')
# obj_dec = np.asarray(obj.coord_dec, dtype='float')

# wh = np.where(obj_parentObjectId == sel_objid)[0]
# print(len(wh), "child objects found for parentObjectId", sel_objid)

# for i in wh:
#     print('%20s %20s %10.3f %9.3f %10.6f %11.6f' %
#           (obj_parentObjectId[i], obj_objectId[i], obj_x[i], obj_y[i],
#            obj_ra[i], obj_dec[i]))

Obtain a cutout at the user-specified location.

In [27]:
cutout = cutout_coadd(butler, ra, dec, band='i', datasetType='deepCoadd',
                      cutoutSideLength=201)
bbox =  (minimum=(23384, 3110), maximum=(23584, 3310)) xy =  (23484, 3210) cutoutSize =  (201, 201)

Display a cutout at the location of this parent, and use the object data retrieved from the butler to mark the child locations.

In [28]:
fig = plt.figure(figsize=(6, 6))
display = afwDisplay.Display(frame=fig)
display.scale('asinh', 'zscale')
display.mtv(cutout.image)
with display.Buffering():
    display.dot('o', table['x'][0], table['y'][0], size=5,
                ctype=afwDisplay.YELLOW)
    for i in range(len(children)):
        display.dot('o', children.iloc[i].x, children.iloc[i].y,
                    size=6, ctype=afwDisplay.GREEN)
plt.gca().axis('off')
display.show_colorbar(False)
plt.show()
No description has been provided for this image

Figure 2: A cutout image displayed in grayscale, with a yellow circle to mark the parent location and green circles to mark the children.

In [29]:
del table, obj, cutout

3. Deblended footprints¶

Scarlet, when run as part of the LSST Science Pipelines, produces the deepCoadd_deblendedFlux data product (among other outputs) after using the multi-wavelength images to identify peaks. This data product contains the footprints for the individual deblended children, which might be a useful data product for some users.

The concept of a footprint was introduced in tutorial 05. To quote Bosch et al. (2018),

Footprints record the exact above-threshold detection region on a CCD. These are similar to Source Extractor’s “segmentation map", in that they identify which pixels belong to which detected objects.

This quote draws an analogy between footprints and segmentation maps, since they both identify pixels with values above some threshold. This is a useful similarity, since it gives us a place to start understanding the properties of footprints.

3.1. The deepCoadd_deblendedFlux table¶

Below, find the deepCoadd_deblendedFlux data product in the butler by searching for all dataset types with the word "blend" in the title.

In [30]:
registry = butler.registry
for dt in sorted(registry.queryDatasetTypes('*blend*')):
    print(dt)
DatasetType('deblend_config', {}, Config)
DatasetType('deblend_log', {skymap, tract, patch}, ButlerLogRecords)
DatasetType('deblend_metadata', {skymap, tract, patch}, PropertySet)
DatasetType('deblend_single_config', {}, Config)
DatasetType('deblend_single_log', {band, skymap, tract, patch}, ButlerLogRecords)
DatasetType('deblend_single_metadata', {band, skymap, tract, patch}, TaskMetadata)
DatasetType('deepCoadd_deblendedCatalog', {skymap, tract, patch}, SourceCatalog)
DatasetType('deepCoadd_deblendedFlux', {band, skymap, tract, patch}, SourceCatalog)
DatasetType('deepCoadd_deblendedFlux_schema', {}, SourceCatalog)
DatasetType('deepCoadd_deblendedModel', {band, skymap, tract, patch}, SourceCatalog)
DatasetType('deepCoadd_deblendedModel_schema', {}, SourceCatalog)
DatasetType('deepCoadd_mcalmax_deblended', {skymap, tract, patch}, SourceCatalog)
DatasetType('deepCoadd_mcalmax_deblended_schema', {}, SourceCatalog)
DatasetType('deepCoadd_ngmix_deblended', {skymap, tract, patch}, SourceCatalog)
DatasetType('deepCoadd_ngmix_deblended_schema', {}, SourceCatalog)
DatasetType('injected_deepCoadd_deblendedCatalog', {skymap, tract, patch}, SourceCatalog)

Look at the schema for the deepCoadd_deblendedFlux catalog to learn more about its contents.

Use the dataId as defined above for the 13-child parent galaxy of interest.

In [31]:
dataId
Out[31]:
{'tract': 2897, 'patch': 5, 'band': 'i'}

We can first look at the contexts of deepCoadd_deblendedFlux by looking at the column names in deepCoadd_deblendedFlux_schema without having to pull the contents for the dataID from the butler.

In [32]:
dbFlux_schema = butler.get('deepCoadd_deblendedFlux_schema', dataId=dataId)
for colname in dbFlux_schema.asAstropy().colnames:
    print(colname)
id
coord_ra
coord_dec
parent
merge_footprint_i
merge_footprint_r
merge_footprint_z
merge_footprint_y
merge_footprint_g
merge_footprint_u
merge_footprint_sky
merge_peak_i
merge_peak_r
merge_peak_z
merge_peak_y
merge_peak_g
merge_peak_u
merge_peak_sky
deblend_runtime
deblend_iterations
deblend_nChild
deblend_deblendedAsPsf
deblend_tooManyPeaks
deblend_parentTooBig
deblend_masked
deblend_sedConvergenceFailed
deblend_morphConvergenceFailed
deblend_blendConvergenceFailedFlag
deblend_edgePixels
deblend_failed
deblend_error
deblend_skipped
deblend_peak_center_x
deblend_peak_center_y
deblend_peakId
deblend_peak_instFlux
deblend_modelType
deblend_nPeaks
deblend_parentNPeaks
deblend_parentNChild
deblend_scarletFlux
deblend_logL
deblend_spectrumInitFlag

Obtain the catalog contents for the deepCoadd_deblendedFlux table in the tract and patch defined by our dataId.

In [33]:
dbFlux = butler.get('deepCoadd_deblendedFlux', dataId=dataId)
dbFlux_table = dbFlux.asAstropy()
print('Retrieved', len(dbFlux_table),
      'records from the deepCoadd_deblendedFlux table.')
Retrieved 32101 records from the deepCoadd_deblendedFlux table.

For the 13 children associated with the parent identified in Section 2.2, display the catalog content.

In [34]:
tx = np.where(dbFlux_table['parent'] == sel_objid)[0]
dbFlux_table[tx]
Out[34]:
Table length=13
idcoord_racoord_decparentmerge_footprint_imerge_footprint_rmerge_footprint_zmerge_footprint_ymerge_footprint_gmerge_footprint_umerge_footprint_skymerge_peak_imerge_peak_rmerge_peak_zmerge_peak_ymerge_peak_gmerge_peak_umerge_peak_skydeblend_runtimedeblend_iterationsdeblend_nChilddeblend_deblendedAsPsfdeblend_tooManyPeaksdeblend_parentTooBigdeblend_maskeddeblend_sedConvergenceFaileddeblend_morphConvergenceFaileddeblend_blendConvergenceFailedFlagdeblend_edgePixelsdeblend_faileddeblend_errordeblend_skippeddeblend_peak_center_xdeblend_peak_center_ydeblend_peakIddeblend_peak_instFluxdeblend_modelTypedeblend_nPeaksdeblend_parentNPeaksdeblend_parentNChilddeblend_scarletFluxdeblend_logLdeblend_spectrumInitFlag
radradpixpixct
int64float64float64int64boolboolboolboolboolboolboolboolboolboolboolboolboolboolfloat32int32int32boolboolboolboolboolboolboolboolboolstr25boolint32int32int32float64str25int32int32int32float32float32bool
1248675773246640703nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueTrueTrueTrueFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234853210472925.39021635055542MultiExtendedSource11313490.87234nanTrue
1248675773246640704nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueTrueTrueFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234673234472942.3927512168884277MultiExtendedSource11313106.880486nanTrue
1248675773246640705nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueTrueTrueTrueFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234833246412420.33462783694267273CompactExtendedSource1131312.085948nanTrue
1248675773246640706nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueFalseTrueFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse235133206472910.3295254409313202CompactExtendedSource1131312.935391nanTrue
1248675773246640707nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234553242472950.26085060834884644CompactExtendedSource113135.9582796nanTrue
1248675773246640708nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueFalseTrueFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234883231472930.24051915109157562CompactExtendedSource1131310.048364nanTrue
1248675773246640709nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseFalseFalseFalseTrueFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234593231504030.3871551752090454CompactExtendedSource1131322.802235nanTrue
1248675773246640710nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueFalseFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234953185409930.1430944949388504CompactExtendedSource113133.7953627nanTrue
1248675773246640711nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueFalseFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse235023188410070.11195013672113419CompactExtendedSource113132.7816982nanTrue
1248675773246640712nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseTrueFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234663198523790.02966391295194626CompactExtendedSource113131.5007979nanTrue
1248675773246640713nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseTrueFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234793227523821.6886492347454422e-21CompactExtendedSource113134.1422963e-20nanTrue
1248675773246640714nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseTrueFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse235033202523800.08955945819616318CompactExtendedSource113132.5225735nanTrue
1248675773246640715nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseTrueFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234633244523870.14773274958133698CompactExtendedSource113134.0033736nanTrue

3.2. Exploring deblended footprints¶

Use the .getFootprint() method to get the footprints for all records in dbFlux, and add them to the astropy table made from the deepCoadd_deblendedFlux table. A new column called 'footprint' will appear in the dbFlux_table.

In [35]:
footprints = [src.getFootprint() for src in dbFlux]
dbFlux_table["footprint"] = footprints

For the 13 children associated with the parent identified in Section 2.2, display the catalog content, which now contains a footprint column (scroll all the way over to the right).

In [36]:
tx = np.where(dbFlux_table['parent'] == sel_objid)[0]
dbFlux_table[tx]
Out[36]:
Table length=13
idcoord_racoord_decparentmerge_footprint_imerge_footprint_rmerge_footprint_zmerge_footprint_ymerge_footprint_gmerge_footprint_umerge_footprint_skymerge_peak_imerge_peak_rmerge_peak_zmerge_peak_ymerge_peak_gmerge_peak_umerge_peak_skydeblend_runtimedeblend_iterationsdeblend_nChilddeblend_deblendedAsPsfdeblend_tooManyPeaksdeblend_parentTooBigdeblend_maskeddeblend_sedConvergenceFaileddeblend_morphConvergenceFaileddeblend_blendConvergenceFailedFlagdeblend_edgePixelsdeblend_faileddeblend_errordeblend_skippeddeblend_peak_center_xdeblend_peak_center_ydeblend_peakIddeblend_peak_instFluxdeblend_modelTypedeblend_nPeaksdeblend_parentNPeaksdeblend_parentNChilddeblend_scarletFluxdeblend_logLdeblend_spectrumInitFlagfootprint
radradpixpixct
int64float64float64int64boolboolboolboolboolboolboolboolboolboolboolboolboolboolfloat32int32int32boolboolboolboolboolboolboolboolboolstr25boolint32int32int32float64str25int32int32int32float32float32boolobject
1248675773246640703nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueTrueTrueTrueFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234853210472925.39021635055542MultiExtendedSource11313490.87234nanTrue1 peaks, area=5840, centroid=(23484.5, 3217.97)
1248675773246640704nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueTrueTrueFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234673234472942.3927512168884277MultiExtendedSource11313106.880486nanTrue1 peaks, area=4329, centroid=(23479.5, 3223.64)
1248675773246640705nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueTrueTrueTrueFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234833246412420.33462783694267273CompactExtendedSource1131312.085948nanTrue1 peaks, area=3658, centroid=(23485.1, 3232.58)
1248675773246640706nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueFalseTrueFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse235133206472910.3295254409313202CompactExtendedSource1131312.935391nanTrue1 peaks, area=3190, centroid=(23497.6, 3211.93)
1248675773246640707nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234553242472950.26085060834884644CompactExtendedSource113135.9582796nanTrue1 peaks, area=1951, centroid=(23467.6, 3233.05)
1248675773246640708nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueTrueTrueFalseTrueFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234883231472930.24051915109157562CompactExtendedSource1131310.048364nanTrue1 peaks, area=4761, centroid=(23485.3, 3224.45)
1248675773246640709nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseFalseFalseFalseTrueFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234593231504030.3871551752090454CompactExtendedSource1131322.802235nanTrue1 peaks, area=3360, centroid=(23473, 3224.49)
1248675773246640710nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueFalseFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234953185409930.1430944949388504CompactExtendedSource113133.7953627nanTrue1 peaks, area=2585, centroid=(23491.2, 3199.54)
1248675773246640711nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseTrueFalseFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse235023188410070.11195013672113419CompactExtendedSource113132.7816982nanTrue1 peaks, area=2094, centroid=(23495.7, 3198.87)
1248675773246640712nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseTrueFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234663198523790.02966391295194626CompactExtendedSource113131.5007979nanTrue1 peaks, area=3235, centroid=(23475.7, 3206.57)
1248675773246640713nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseTrueFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234793227523821.6886492347454422e-21CompactExtendedSource113134.1422963e-20nanTrue1 peaks, area=4270, centroid=(23482.9, 3227.15)
1248675773246640714nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseTrueFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse235033202523800.08955945819616318CompactExtendedSource113132.5225735nanTrue1 peaks, area=2791, centroid=(23497.3, 3207.24)
1248675773246640715nannan1248675773246610542TrueTrueTrueTrueTrueTrueFalseFalseTrueFalseFalseFalseFalseFalse0.000FalseFalseFalseFalseFalseFalseTrueFalseFalseFalse234633244523870.14773274958133698CompactExtendedSource113134.0033736nanTrue1 peaks, area=2388, centroid=(23474.9, 3235.69)

Use the function heavyFootprint2Image to turn the footprint into a heavyFootprint image. The function returns an image defined by a bounding box, and padded with fill value (the default is NaN) which contains the projected flux in heavy.

The term "heavy" here refers to footprint pixels that aren't simply populated with values 0 or 1 (where 1 indicates the pixel is part of the footprint), but with values that represent the flux.

In [37]:
fpimg = heavyFootprint2Image(dbFlux_table['footprint'][tx[0]])

Optional: Explore the properties of fpimg by uncommenting the line below, putting your cursor after the period, pressing tab, and scrolling through the pop-up box of attributes. Recognize familiar image attributes such as image and getBBox.

In [38]:
# fpimg.

Display the footprint of each of the 13 deblended children in a 3x5 grid.

Title the subplot with the index and the footprint shape. Notice that the footprint image areas are not a standard size: they encompass the footprint.

Remove the axis labels (x and y pixels) and the colorbar scale. Note that this grid leaves 2 subplots empty, so erase the axes for the final two subplots.

In [39]:
fig, ax = plt.subplots(3, 5, figsize=(10, 7))
display = afwDisplay.Display(frame=fig)

x = 0
for i in range(3):
    if i < 2:
        Nc = 5
    elif i == 2:
        Nc = 3
    for j in range(Nc):
        fpimg = heavyFootprint2Image(dbFlux_table['footprint'][tx[x]])
        plt.sca(ax[i, j])
        display.scale('asinh', 'zscale')
        display.mtv(fpimg.image)
        display.show_colorbar(False)
        plt.title(str(x) + " size:" + str(np.shape(fpimg.image.array)),
                  fontsize=10)
        plt.gca().axis('off')
        x += 1
        del fpimg

plt.sca(ax[2, 3])
display = afwDisplay.Display(frame=fig)
plt.gca().axis('off')
plt.sca(ax[2, 4])
display = afwDisplay.Display(frame=fig)
plt.gca().axis('off')

plt.tight_layout()
plt.show()
No description has been provided for this image

Figure 3: The heavy footprints, displayed in grayscale, for each of the 13 children.

The above grid is somewhat useful for seeing the shapes of the individual footprints, but it does not show how the footprints might overlap with each other.

Below, plot all of the footprints together in a single axis.

Define a set of 13 colormaps to use, one for each child footprint, and label each child footprint 0 through 12.

The alpha parameter is used here to set the pixel transparency in a fairly crude way, so that pixels are semi-transparent in the bright (high flux) areas of the footprint, and totally transparent in the outskirts of the footprint.

To make sure the final plot is big enough to hold all footprints, keep track of the min/max x/y values, and then use them to set the plot limits.

Note: Due to the scaling, it seems like not all of these footprints touch, but since they are all deblended from a single parent, they do in fact join and overlap. Adjust the values of alpha or try different vmin and vmax scaling to explore the footprint edges.

In [40]:
my_colormaps = ['Greys', 'Purples', 'Blues', 'Greens', 'Oranges',
                'Reds', 'YlOrBr', 'RdPu', 'GnBu', 'YlGnBu',
                'viridis', 'cividis', 'cool']
xmin = 9999999.
xmax = 0.
ymin = 9999999.
ymax = 0.

fig = plt.figure(figsize=(6, 6))
for x in range(len(tx)):
    fpimg = heavyFootprint2Image(dbFlux_table['footprint'][tx[x]])
    fpimg_extent = (fpimg.getBBox().beginX, fpimg.getBBox().endX,
                    fpimg.getBBox().beginY, fpimg.getBBox().endY)
    alphas = np.asarray(fpimg.image.array,
                        dtype='float') / np.nanmax(fpimg.image.array)
    tmp_x = np.where((np.isnan(alphas)) | (alphas < 0.1))
    alphas[tmp_x] = 0.0
    tmp_x = np.where(alphas >= 0.1)
    alphas[tmp_x] = 0.7
    im = plt.imshow(fpimg.image.array, cmap=my_colormaps[x], alpha=alphas,
                    vmin=np.nanmin(fpimg.image.array),
                    vmax=np.nanmax(fpimg.image.array),
                    extent=fpimg_extent, origin='lower')
    if xmin > fpimg.getBBox().beginX:
        xmin = fpimg.getBBox().beginX
    if xmax < fpimg.getBBox().endX:
        xmax = fpimg.getBBox().endX
    if ymin > fpimg.getBBox().beginY:
        ymin = fpimg.getBBox().beginY
    if ymax < fpimg.getBBox().endY:
        ymax = fpimg.getBBox().endY
    del fpimg, fpimg_extent, alphas, tmp_x, im

    center_x = dbFlux_table['deblend_peak_center_x'][tx[x]]
    center_y = dbFlux_table['deblend_peak_center_y'][tx[x]]
    plt.text(center_x, center_y, str(x), fontsize=10)

plt.xlim([xmin, xmax])
plt.ylim([ymin, ymax])
plt.xlabel('x', fontsize=10)
plt.ylabel('y', fontsize=10)
plt.tick_params(labelsize=10)
plt.show()
No description has been provided for this image

Figure 4: The heavy footprints from Figure 3, each with their own colormap applied (instead of grayscale) and labeled as 0 through 12, to distinguish them. Instead of showing each heavy footprint separately, they've been pasted back into the image frame.

In [41]:
del dataId, dbFlux_schema, dbFlux, dbFlux_table
del footprints

4. Use-case: SN Ia host galaxy identification¶

DP0 Portal tutorial 03 investigated the location of a Type Ia supernova (SN Ia) near a bright host galaxy and found that in some filters, and with some image scaling parameters, it seemed like the true host is a fainter host that is blended with a bigger, brighter galaxy.

The following steps use the techniques above to look for parent and child sources near this SN Ia.

Define the coordinates for this SN Ia.

In [42]:
sn_ra = 67.4579
sn_dec = -44.0802

Obtain the i-band deepCoadd image, which in Step 2.3 of Portal tutorial 03 reveals a potentially blended host.

In [43]:
cutout = cutout_coadd(butler, sn_ra, sn_dec, band='i', datasetType='deepCoadd',
                      cutoutSideLength=71)
bbox =  (minimum=(9577, 10429), maximum=(9647, 10499)) xy =  (9612, 10464) cutoutSize =  (71, 71)

Get the WCS for this cutout and convert the SN Ia coordinates from RA, Dec to pixels.

In [44]:
wcs = cutout.getWcs()
sn_radec = geom.SpherePoint(sn_ra, sn_dec, geom.degrees)
sn_xy = wcs.skyToPixel(sn_radec)

Display the i-band deepCoadd with the SN Ia location marked as a pink circle.

See how it seems like there's a faint galaxy there, blended with the larger one?

Try changing the scale from asinh to linear to get a different view.

In [45]:
fig = plt.figure(figsize=(4, 4))
display = afwDisplay.Display(frame=fig)
display.scale('asinh', 'zscale')
display.mtv(cutout.image)
with display.Buffering():
    display.dot('o', sn_xy.getX(), sn_xy.getY(), size=4,
                ctype=afwDisplay.MAGENTA)

plt.gca().axis('off')
display.show_colorbar(False)
plt.show()
No description has been provided for this image

Figure 5: The cutout, in grayscale, at the location of a supernova (magenta circle).

Query the Object catalog for extended parents in the area. As a reminder, the spatial constraint by searching around a specified ra/dec is not technically necessary, but speeds up the query significantly because of the way the data in the object Table are stored (see Section 2.1).

In [46]:
query = "SELECT objectId, coord_ra, coord_dec, x, y, tract, " + \
        "patch, refExtendedness, detect_isPrimary, detect_isIsolated, " + \
        "deblend_nChild, detect_fromBlend, footprintArea, parentObjectId " + \
        "FROM dp02_dc2_catalogs.Object " + \
        "WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), " + \
        "CIRCLE('ICRS', " + str(sn_ra) + ", " + str(sn_dec) + \
        ", 0.005)) = 1 " + \
        "AND refExtendedness = 1 AND deblend_nChild > 0"
print(query)
SELECT objectId, coord_ra, coord_dec, x, y, tract, patch, refExtendedness, detect_isPrimary, detect_isIsolated, deblend_nChild, detect_fromBlend, footprintArea, parentObjectId FROM dp02_dc2_catalogs.Object WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), CIRCLE('ICRS', 67.4579, -44.0802, 0.005)) = 1 AND refExtendedness = 1 AND deblend_nChild > 0
In [47]:
job = service.submit_job(query)
job.run()
job.wait(phases=['COMPLETED', 'ERROR'])
print('Job phase is', job.phase)
Job phase is COMPLETED
In [48]:
parents = job.fetch_result().to_table()
In [49]:
parents
Out[49]:
Table length=6
objectIdcoord_racoord_decxytractpatchrefExtendednessdetect_isPrimarydetect_isIsolateddeblend_nChilddetect_fromBlendfootprintAreaparentObjectId
degdegpixpixpix
int64float64float64float64float64int64int64float64boolboolint32boolint32int64
125222059873456571167.456615-44.08243519628.957156410423.54263492905161.0TrueTrue1False670
125222059873456569067.4549172-44.08385679651.015289410398.04339222905161.0TrueTrue1False1600
125222059873456567967.4590361-44.08489419597.834066610379.1505082905161.0TrueTrue1False1410
125222059873455960067.4529129-44.07865559676.552329310491.77235592905161.0TrueTrue1False890
125222059873455972367.4553039-44.075869645.42787710541.96507512905161.0TrueTrue1False3800
125222059873455934867.4570197-44.08030579623.567357810461.84999882905161.0FalseFalse32False114940

The table above reveals there is one nearby parent, with 32 children (identified by the deblend_nChild keyword). The other 5 sources you can see are not blends because they have detect_isIsolated = True.

Plot the location of this parent on the deepCoadd to see if it's the bright nearby galaxy.

In [50]:
tx = np.where(parents['deblend_nChild'] == 32)[0]
print('objectId of parent with 32 children: ', parents['objectId'][tx[0]])

fig = plt.figure(figsize=(4, 4))
display = afwDisplay.Display(frame=fig)
display.scale('asinh', 'zscale')
display.mtv(cutout.image)
with display.Buffering():
    display.dot('o', sn_xy.getX(), sn_xy.getY(), size=4,
                ctype=afwDisplay.MAGENTA)
    display.dot('o', parents['x'][tx], parents['y'][tx], size=5,
                ctype=afwDisplay.YELLOW)

plt.gca().axis('off')
display.show_colorbar(False)
plt.show()
objectId of parent with 32 children:  1252220598734559348
No description has been provided for this image

Figure 6: Same as Figure 5, but with a yellow circle to mark a nearby parent object.

Query for the 32 children of this parent.

In [51]:
query = "SELECT objectId, coord_ra, coord_dec, x, y, tract, patch, " + \
        "refExtendedness, detect_isPrimary, deblend_nChild, " + \
        "detect_fromBlend, footprintArea, parentObjectId " + \
        "FROM dp02_dc2_catalogs.Object " + \
        "WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), " + \
        "CIRCLE('ICRS', " + str(sn_ra) + ", " + str(sn_dec) + \
        ", 0.01)) = 1 AND parentObjectId = 1252220598734559348"
print(query)
SELECT objectId, coord_ra, coord_dec, x, y, tract, patch, refExtendedness, detect_isPrimary, deblend_nChild, detect_fromBlend, footprintArea, parentObjectId FROM dp02_dc2_catalogs.Object WHERE CONTAINS(POINT('ICRS', coord_ra, coord_dec ), CIRCLE('ICRS', 67.4579, -44.0802, 0.01)) = 1 AND parentObjectId = 1252220598734559348
In [52]:
job = service.submit_job(query)
job.run()
job.wait(phases=['COMPLETED', 'ERROR'])
print('Job phase is', job.phase)
Job phase is COMPLETED
In [53]:
children = job.fetch_result().to_table()
In [54]:
# children

Plot the locations of the children as green circles.

In [55]:
fig = plt.figure(figsize=(4, 4))
display = afwDisplay.Display(frame=fig)
display.scale('asinh', 'zscale')
display.mtv(cutout.image)
with display.Buffering():
    display.dot('o', sn_xy.getX(), sn_xy.getY(), size=4,
                ctype=afwDisplay.MAGENTA)
    display.dot('o', parents['x'][tx], parents['y'][tx], size=5,
                ctype=afwDisplay.YELLOW)
    for i in range(len(children)):
        display.dot('o', children['x'][i], children['y'][i], size=3,
                    ctype=afwDisplay.GREEN)

plt.gca().axis('off')
display.show_colorbar(False)
plt.show()
No description has been provided for this image

Figure 7: Same as Figure 6, but with green circles to mark the children.

Well! What seems to be the true host galaxy has not been deblended from the large, bright, nearby galaxy.

This might be a good case where the deblender should be run, with more aggressive deblending parameters, but that is an intermediate/advanced level use of the LSST Science Pipelines that is left to a different tutorial.

5. Exercise for the learner¶

Further explore the footprints of the children around this SN Ia. Create a footprints plot similar to the one in Section 3.2.

In [ ]: