22 __all__ = [
"CatalogExposure",
"MultibandFitSubTask",
"MultibandFitConfig",
"MultibandFitTask"]
24 from abc
import ABC, abstractmethod
25 from dataclasses
import dataclass, field
28 import lsst.daf.butler
as dafButler
32 from typing
import Dict, Iterable, List, Optional, Set
35 @dataclass(frozen=True)
39 return self.dataId[
'band']
42 def calib(self) -> Optional[afwImage.PhotoCalib]:
43 return None if self.exposure
is None else self.exposure.getPhotoCalib()
45 catalog: Optional[afwTable.SourceCatalog]
46 exposure: Optional[afwImage.Exposure]
47 dataId: dafButler.DataCoordinate
48 metadata: Dict = field(default_factory=dict)
51 if 'band' not in self.dataId:
52 raise ValueError(f
'dataId={self.dataId} must have a band')
55 multibandFitBaseTemplates = {
56 "name_input_coadd":
"deep",
57 "name_output_coadd":
"deep",
58 "name_output_cat":
"fit",
63 pipeBase.PipelineTaskConnections,
64 dimensions=(
"tract",
"patch",
"skymap"),
65 defaultTemplates=multibandFitBaseTemplates,
68 doc=
"Reference multiband source catalog",
69 name=
"{name_input_coadd}Coadd_ref",
70 storageClass=
"SourceCatalog",
71 dimensions=(
"tract",
"patch",
"skymap"),
74 doc=
"Deblended single-band source catalogs",
75 name=
"{name_input_coadd}Coadd_meas",
76 storageClass=
"SourceCatalog",
78 dimensions=(
"tract",
"patch",
"band",
"skymap"),
81 doc=
"Exposures on which to run fits",
82 name=
"{name_input_coadd}Coadd_calexp",
83 storageClass=
"ExposureF",
85 dimensions=(
"tract",
"patch",
"band",
"skymap"),
87 cat_output = cT.Output(
88 doc=
"Measurement multi-band catalog",
89 name=
"{name_output_coadd}Coadd_{name_output_cat}",
90 storageClass=
"SourceCatalog",
91 dimensions=(
"tract",
"patch",
"skymap"),
93 cat_ref_schema = cT.InitInput(
94 doc=
"Schema associated with a ref source catalog",
95 storageClass=
"SourceCatalog",
96 name=
"{name_input_coadd}Coadd_ref_schema",
98 cat_output_schema = cT.InitOutput(
99 doc=
"Output of the schema used in deblending task",
100 name=
"{name_output_coadd}Coadd_{name_output_cat}_schema",
101 storageClass=
"SourceCatalog"
105 """Validates the `lsst.daf.butler.DatasetRef` bands against the
106 subtask's list of bands to fit and drops unnecessary bands.
110 datasetRefMap : `NamedKeyDict`
111 Mapping from dataset type to a `set` of
112 `lsst.daf.butler.DatasetRef` objects
116 datasetRefMap : `NamedKeyDict`
117 Modified mapping of input with possibly adjusted
118 `lsst.daf.butler.DatasetRef` objects.
123 Raised if any of the per-band datasets have an inconsistent band
124 set, or if the band set to fit is not a subset of the data bands.
129 bands_fit, bands_read_only = self.config.get_band_sets()
130 bands_needed = bands_fit.union(bands_read_only)
135 for type_d, ref_d
in datasetRefMap.items():
137 if 'band' in type_d.dimensions:
138 bands_set = {dref.dataId[
'band']
for dref
in ref_d}
139 if bands_data
is None:
140 bands_data = bands_set
141 if bands_needed != bands_data:
142 if not bands_needed.issubset(bands_data):
144 f
'Datarefs={ref_d} have data with bands in the set={bands_set},'
145 f
'which is not a subset of the required bands={bands_needed} defined by '
146 f
'{self.config.__class__}.fit_multiband='
147 f
'{self.config.fit_multiband._value.__class__}\'s attributes'
148 f
' bands_fit={bands_fit} and bands_read_only()={bands_read_only}.'
149 f
' Add the required bands={bands_needed.difference(bands_data)}.'
152 bands_extra = bands_data.difference(bands_needed)
153 elif bands_set != bands_data:
155 f
'Datarefs={ref_d} have data with bands in the set={bands_set}'
156 f
' which differs from the previous={bands_data}); bandsets must be identical.'
160 if dref.dataId[
'band']
in bands_extra:
166 """Config class for the MultibandFitTask to define methods returning
167 values that depend on multiple config settings.
171 """Return the set of bands that the Task needs to read (e.g. for
172 defining priors) but not necessarily fit.
176 The set of such bands.
182 """An abstract interface for subtasks of MultibandFitTask to perform
183 multiband fitting of deblended sources.
187 schema : `lsst.afw.table.Schema`
188 The input schema for the reference source catalog, used to initialize
191 Additional arguments to be passed to the `lsst.pipe.base.Task`
194 ConfigClass = MultibandFitSubConfig
196 def __init__(self, schema: afwTable.Schema, **kwargs):
201 self, catexps: Iterable[CatalogExposure], cat_ref: afwTable.SourceCatalog
202 ) -> pipeBase.Struct:
203 """Fit sources from a reference catalog using data from multiple
204 exposures in the same patch.
208 catexps : `typing.List [CatalogExposure]`
209 A list of catalog-exposure pairs in a given band.
210 cat_ref : `lsst.afw.table.SourceCatalog`
211 A reference source catalog to fit.
215 retStruct : `lsst.pipe.base.Struct`
216 A struct with a cat_output attribute containing the output
221 Subclasses may have further requirements on the input parameters,
223 - Passing only one catexp per band;
224 - Catalogs containing HeavyFootprints with deblended images;
225 - Fitting only a subset of the sources.
226 If any requirements are not met, the subtask should fail as soon as
229 raise NotImplementedError()
234 raise NotImplementedError()
238 pipeBase.PipelineTaskConfig,
239 pipelineConnections=MultibandFitConnections,
241 """Configuration class for the MultibandFitTask, containing a
242 configurable subtask that does all fitting.
244 fit_multiband = pexConfig.ConfigurableField(
245 target=MultibandFitSubTask,
246 doc=
"Task to fit sources using multiple bands",
250 """Get the set of bands required by the fit_multiband subtask.
255 The set of bands that the subtask will fit.
256 bands_read_only : `set`
257 The set of bands that the subtask will only read data (measurement catalog and exposure) for.
261 except AttributeError:
262 raise RuntimeError(f
'{__class__}.fit_multiband must have bands_fit attribute')
from None
263 bands_read_only = self.
fit_multibandfit_multiband.bands_read_only()
264 return set(bands_fit), set(bands_read_only)
268 ConfigClass = MultibandFitConfig
269 _DefaultName =
"multibandFit"
272 super().
__init__(initInputs=initInputs, **kwargs)
273 self.makeSubtask(
"fit_multiband", schema=initInputs[
"cat_ref_schema"].schema)
277 inputs = butlerQC.get(inputRefs)
278 input_refs_objs = [(inputRefs.cats_meas, inputs[
'cats_meas']), (inputRefs.coadds, inputs[
'coadds'])]
280 {dRef.dataId: obj
for dRef, obj
in zip(refs, objs)}
281 for refs, objs
in input_refs_objs
283 dataIds = set(cats).union(set(exps))
285 CatalogExposure(catalog=cats.get(dataId), exposure=exps.get(dataId), dataId=dataId)
286 for dataId
in dataIds
288 outputs = self.
runrun(catexps=catexps, cat_ref=inputs[
'cat_ref'])
289 butlerQC.put(outputs, outputRefs)
292 raise RuntimeError(f
'{__class__}.config.fit_multiband.run schema != initOutput schema:'
293 f
' {outputs.cat_output.schema} vs {self.cat_output_schema.schema}')
295 def run(self, catexps: List[CatalogExposure], cat_ref: afwTable.SourceCatalog) -> pipeBase.Struct:
296 """Fit sources from a reference catalog using data from multiple
297 exposures in the same region (patch).
301 catexps : `typing.List [CatalogExposure]`
302 A list of catalog-exposure pairs in a given band.
303 cat_ref : `lsst.afw.table.SourceCatalog`
304 A reference source catalog to fit.
308 retStruct : `lsst.pipe.base.Struct`
309 A struct with a cat_output attribute containing the output
314 Subtasks may have further requirements; see `MultibandFitSubTask.run`.
316 cat_output = self.fit_multiband.
run(catexps, cat_ref).output
317 retStruct = pipeBase.Struct(cat_output=cat_output)
Optional[afwImage.PhotoCalib] calib(self)
def adjustQuantum(self, datasetRefMap)
Set bands_read_only(self)
pipeBase.Struct run(self, Iterable[CatalogExposure] catexps, afwTable.SourceCatalog cat_ref)
afwTable.Schema schema(self)
def __init__(self, afwTable.Schema schema, **kwargs)
pipeBase.Struct run(self, List[CatalogExposure] catexps, afwTable.SourceCatalog cat_ref)
def runQuantum(self, butlerQC, inputRefs, outputRefs)
def __init__(self, initInputs, **kwargs)