-
Notifications
You must be signed in to change notification settings - Fork 23
DM-55521: update coadd processing tasks to work with lsst.images #1334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
156383a
f2a1ddb
6150aac
5994152
24cb0b0
8a2832e
627264b
bfb8637
82b9d23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,17 +21,21 @@ | |
|
|
||
| __all__ = ["DeblendCoaddSourcesMultiConfig", "DeblendCoaddSourcesMultiTask"] | ||
|
|
||
| import dataclasses | ||
|
|
||
| import numpy as np | ||
|
|
||
| from lsst.pipe.base import PipelineTask, PipelineTaskConfig, PipelineTaskConnections | ||
| import lsst.pipe.base.connectionTypes as cT | ||
|
|
||
| from lsst.pex.config import ConfigurableField, Field | ||
| from lsst.pex.config import ChoiceField, ConfigurableField, Field | ||
| from lsst.meas.base import SkyMapIdGeneratorConfig | ||
| from lsst.meas.extensions.scarlet import ScarletDeblendTask | ||
|
|
||
| import lsst.afw.image as afwImage | ||
| import lsst.afw.table as afwTable | ||
| import lsst.images as imgs | ||
| from lsst.images.cells import CellCoadd | ||
|
|
||
| from .coaddBase import reorderRefs | ||
|
|
||
|
|
@@ -112,12 +116,16 @@ class DeblendCoaddSourcesMultiConnections(PipelineTaskConnections, | |
|
|
||
| def __init__(self, *, config=None): | ||
| super().__init__(config=config) | ||
| if config: | ||
| if config.useCellCoadds: | ||
| del self.coadds | ||
| else: | ||
| del self.coadds_cell | ||
| del self.backgrounds | ||
| if self.config.imageType == "future": | ||
| self.coadds = dataclasses.replace(self.coadds, storageClass="CellCoadd") | ||
| self.deconvolvedCoadds = dataclasses.replace(self.deconvolvedCoadds, storageClass="MaskedImageV2") | ||
| del self.coadds_cell | ||
| del self.backgrounds | ||
| elif self.config.useCellCoadds: | ||
| del self.coadds | ||
| else: | ||
| del self.coadds_cell | ||
| del self.backgrounds | ||
|
|
||
|
|
||
| class DeblendCoaddSourcesMultiConfig(PipelineTaskConfig, | ||
|
|
@@ -131,6 +139,25 @@ class DeblendCoaddSourcesMultiConfig(PipelineTaskConfig, | |
| doc="Task to deblend an images in multiple bands" | ||
| ) | ||
| idGenerator = SkyMapIdGeneratorConfig.make_field() | ||
| imageType = ChoiceField( | ||
| "Which image type to expect for the input coadds. " | ||
| "This option only directly affects connection storage classes and hence 'runQuantum'; the 'run' " | ||
| "method behavior is determined by which type is actually passed in.", | ||
| allowed={ | ||
| "legacy": ( | ||
| "Read a lsst.cell_coadds.MultipleCellCoadd via 'coadds_cells` and restore 'background' " | ||
| "(if useCellCoadd) or lsst.afw.image.Exposure via `coadds` (if not useCellCoadd), and read " | ||
| "lsst.afw.image.Exposure via 'deconvolvedCoadds'." | ||
| ), | ||
| "future": ( | ||
| "Read lsst.images.cells.CellCoadd via 'coadds' and lsst.images.MaskedImage via " | ||
| "'deconvolvedCoadds'. The useCellCoadds options is ignored." | ||
| ), | ||
| }, | ||
| dtype=str, | ||
| optional=False, | ||
| default="legacy", | ||
| ) | ||
|
|
||
|
|
||
| class DeblendCoaddSourcesMultiTask(PipelineTask): | ||
|
|
@@ -160,17 +187,25 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs): | |
| inputs = butlerQC.get(inputRefs) | ||
| bands = [dRef.dataId["band"] for dRef in deconvolvedRefs] | ||
| mergedDetections = inputs.pop("mergedDetections") | ||
| if self.config.useCellCoadds: | ||
| exposures = [mcc.stitch().asExposure() for mcc in inputs.pop("coadds_cell")] | ||
| backgrounds = inputs.pop("backgrounds") | ||
| for exposure, background in zip(exposures, backgrounds): | ||
| exposure.image -= background.getImage() | ||
| coadds = exposures | ||
| else: | ||
| coadds = inputs.pop("coadds") | ||
| match self.config.imageType: | ||
| case "legacy": | ||
| if self.config.useCellCoadds: | ||
| exposures = [mcc.stitch().asExposure() for mcc in inputs.pop("coadds_cell")] | ||
| backgrounds = inputs.pop("backgrounds") | ||
| for exposure, background in zip(exposures, backgrounds): | ||
| exposure.image -= background.getImage() | ||
| coadds = exposures | ||
| coaddRefs = inputRefs.coadds_cell | ||
| else: | ||
| coadds = inputs.pop("coadds") | ||
| coaddRefs = inputRefs.coadds | ||
| case "future": | ||
| coadds = inputs.pop("coadds") # conversion deferred to run(). | ||
| coaddRefs = inputRefs.coadds | ||
| case _: | ||
| raise AssertionError(f"Invalid choice {self.config.imageType!r} for imageType.") | ||
|
|
||
| # Ensure that the coadd bands and deconvolved coadd bands match | ||
| coaddRefs = inputRefs.coadds_cell if self.config.useCellCoadds else inputRefs.coadds | ||
| coaddBands = [dRef.dataId["band"] for dRef in coaddRefs] | ||
| if bands != coaddBands: | ||
| self.log.error("Coadd bands %s != deconvolved coadd bands %s", bands, coaddBands) | ||
|
|
@@ -194,12 +229,47 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs): | |
| butlerQC.put(outputs, outputRefs) | ||
|
|
||
| def run(self, coadds, bands, mergedDetections, deconvolvedCoadds, idFactory): | ||
| """Deblend coadds from multiple bands together. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| coadds : `list` [`lsst.afw.image.Exposure` | \ | ||
| `lsst.images.cells.CellCoadd`] | ||
| Coadds to deblend. | ||
| bands : `list` [`str`] | ||
| Names or the bands for ``coadds`` (zip-iteration compatible). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there lists that aren't zip-iteration compatible, or do you just mean that the bands have to be in the same order as the coadds?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I mean it should have corresponding elements in the same order. |
||
| mergedDetections : `lsst.afw.table.SourceCatalog` | ||
| Input catalog of detections, already merged across bands. | ||
| deconvolvedCoadds : `list` [`lsst.afw.image.Exposure` | \ | ||
| `lsst.images.MaskedImage`] | ||
| Deconvolved versions of ``coadds`` (zip-iteration compatible). | ||
| idFactory : `lsst.afw.table.IdFactory` | ||
| Factory used to generate output source IDs. | ||
|
|
||
| Returns | ||
| ------- | ||
| struct : `lsst.pipe.base.Struct` | ||
| Unmodified outputs of the ``multibandDeblend`` subtask. | ||
| """ | ||
| coadds = [c.to_legacy() if isinstance(c, CellCoadd) else c for c in coadds] | ||
| deconvolvedCoadds = [self._coerceDeconvolvedInput(d, c) for d, c in zip(deconvolvedCoadds, coadds)] | ||
| sources = self._makeSourceCatalog(mergedDetections, idFactory) | ||
| multiExposure = afwImage.MultibandExposure.fromExposures(bands, coadds) | ||
| mDeconvolved = afwImage.MultibandExposure.fromExposures(bands, deconvolvedCoadds) | ||
| result = self.multibandDeblend.run(multiExposure, mDeconvolved, sources) | ||
| return result | ||
|
|
||
| def _coerceDeconvolvedInput( | ||
| self, deconvolved: afwImage.Exposure | imgs.MaskedImage, coadd: afwImage.Exposure | ||
| ) -> afwImage.Exposure: | ||
| if isinstance(deconvolved, imgs.MaskedImage): | ||
| deconvolved = afwImage.Exposure( | ||
| maskedImage=deconvolved.to_legacy(plane_map=imgs.get_legacy_deep_coadd_mask_planes()), | ||
| exposureInfo=coadd.getInfo(), | ||
| dtype=deconvolved.image.array.dtype, | ||
| ) | ||
| return deconvolved | ||
|
|
||
| def _makeSourceCatalog(self, mergedDetections, idFactory): | ||
| # There may be gaps in the mergeDet catalog, which will cause the | ||
| # source ids to be inconsistent. So we update the id factory | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |
| import lsst.pipe.base as pipeBase | ||
| import lsst.pipe.base.connectionTypes as cT | ||
|
|
||
| import dataclasses | ||
| from abc import ABC, abstractmethod | ||
| from pydantic.dataclasses import dataclass | ||
|
|
||
|
|
@@ -93,7 +94,11 @@ def __init__(self, *, config=None): | |
| if config is None: | ||
| return | ||
|
|
||
| if config.use_cell_coadds: | ||
| if config.image_type == "future": | ||
| self.coadd = dataclasses.replace(self.coadd, storageClass="CellCoadd") | ||
| del self.coadd_cell | ||
| del self.background | ||
| elif config.use_cell_coadds: | ||
| del self.coadd | ||
| else: | ||
| del self.coadd_cell | ||
|
|
@@ -168,6 +173,23 @@ class CoaddPsfFitConfig( | |
| doc="Task to fit PSF models for a single coadd", | ||
| ) | ||
| idGenerator = SkyMapIdGeneratorConfig.make_field() | ||
| image_type = pexConfig.ChoiceField( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to define this once in a module somewhere, or is it safer to duplicate?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docstrings pretty much always differ, even if only slightly sometimes. |
||
| "Which image type to expect for the input coadd. " | ||
| "This option only directly affects connection storage classes and hence 'runQuantum'; the 'run' " | ||
| "method behavior is determined by which type is actually passed in.", | ||
| allowed={ | ||
| "legacy": ( | ||
| "Read a lsst.cell_coadds.MultipleCellCoadd via 'coadd_cell` and restore 'background' " | ||
| "(if use_cell_coadd) or lsst.afw.image.Exposure via `coadd` (if not use_cell_coadd)." | ||
| ), | ||
| "future": ( | ||
| "Read lsst.images.cells.CellCoadd via the 'coadd' connection. use_cell_coadd is ignored." | ||
| ), | ||
| }, | ||
| dtype=str, | ||
| optional=False, | ||
| default="legacy", | ||
| ) | ||
|
|
||
|
|
||
| class CoaddPsfFitTask(pipeBase.PipelineTask): | ||
|
|
@@ -191,7 +213,10 @@ def runQuantum(self, butlerQC, inputRefs, outputRefs): | |
| id_tp = self.config.idGenerator.apply(butlerQC.quantum.dataId).catalog_id | ||
| dataId = inputRefs.cat_meas.dataId | ||
|
|
||
| if self.config.use_cell_coadds: | ||
| if self.config.image_type == "future": | ||
| coaddDataRef = inputRefs.coadd | ||
| exposure = inputs.pop('coadd').to_legacy() | ||
| elif self.config.use_cell_coadds: | ||
| coaddDataRef = inputRefs.coadd_cell | ||
| multiple_cell_coadd = inputs.pop('coadd_cell') | ||
| background = inputs.pop('background') | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, are you anticipating keeping this around and adding to allowed at some point, even after
legacyis deprecated? Or did you mainly want better documentation via the allowed dict than you'd get from a boolean Field?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not planning to keep it around forever, but on this ticket I really wished that
useCellCoaddhad been aChoiceFieldinstead a boolean, because then I would have been able to use that instead of inventing another config field and leavinguseCellCoaddsort of dangling in a lot of cases. So this is probably fighting the last war, but I figured I'd leave room for a plot twist in the migration just in case.