Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions python/lsst/drp/tasks/make_coadd_input_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# This file is part of drp_tasks.
#
# Developed for the LSST Data Management System.
# This product includes software developed by the LSST Project
# (https://www.lsst.org).
# See the COPYRIGHT file at the top-level directory of this distribution
# for details of code ownership.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

__all__ = [
"MakeCoaddInputSummaryTractConnections",
"MakeCoaddInputSummaryTractConfig",
"MakeCoaddInputSummaryTract",
"MakeCoaddInputSummaryConnections",
"MakeCoaddInputSummaryConfig",
"MakeCoaddInputSummary",
]

import numpy as np
from astropy.table import Table

import lsst.pex.config
import lsst.pipe.base
from lsst.obs.base import TableVStack


class MakeCoaddInputSummaryTractConnections(
lsst.pipe.base.PipelineTaskConnections,
dimensions=("skymap", "tract", "band"),
):
coadd_inputs_handles = lsst.pipe.base.connectionTypes.Input(
name="deep_coadd_predetection.coaddInputs",
doc="Coadd inputs.",
storageClass="CoaddInputs",
dimensions=("skymap", "tract", "patch", "band"),
deferLoad=True,
multiple=True,
)
coadd_input_summary_tract = lsst.pipe.base.connectionTypes.Output(
name="coadd_input_summary_tract",
doc="Summary table aggregating coaddInputs from multiple coadds (including "
"all coaddInputs tables in a tract for a single band).",
storageClass="ArrowAstropy",
dimensions=("skymap", "tract", "band"),
)


class MakeCoaddInputSummaryTractConfig(
lsst.pipe.base.PipelineTaskConfig,
pipelineConnections=MakeCoaddInputSummaryTractConnections,
):
pass


class MakeCoaddInputSummaryTract(lsst.pipe.base.PipelineTask):
"""Task to make coadd input summary by tract/band."""

ConfigClass = MakeCoaddInputSummaryTractConfig
_DefaultName = "make_coadd_input_summary_tract"

def run(self, *, coadd_inputs_handles):
"""Run the AggregateCoaddInputsTract task.

Parameters
----------
coadd_inputs : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
List of coadd input handles to aggregate.

Returns
-------
result : `lsst.pipe.base.Struct`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that "weight" is non-obvious, so I was wondering whether the columns could be listed here, with a description for each. Most would be trivial ("visit": "visit ID"), but then we'd have a description for "weight".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now you're asking for me to document long standing undocumented things ... and unfortunately this is a place where nobody would look. I'm not sure what to do.

Result struct containing:
``coadd_input_summary_tract`` : `astropy.table.Table`

Notes
-----
The output table contains the the following columns:

tract : `int`
The tract number.
patch : `int`
The patch number.
visit : `int`
The visit number.
detector : `int`
The detector number.
weight : `float`
The weight that was used as an input to the coadd.
goodpix : `int`
The number of pixels from the visit/detector in the patch.
band : `str`
The band for the visit.
"""
self.log.info("Summarizing %d coadd input catalogs", len(coadd_inputs_handles))
n_inputs = 0
detector_table_dict = {}
bands = set()
for coadd_inputs_handle in coadd_inputs_handles:
data_id = coadd_inputs_handle.dataId
tract = data_id["tract"]
patch = data_id["patch"]
band = data_id["band"]

bands.add(band)

coadd_inputs = coadd_inputs_handle.get()

detector_table_dict[(tract, patch, band)] = coadd_inputs.ccds
n_inputs += len(coadd_inputs.ccds)

self.log.info("Found %d coadd input rows to summarize", n_inputs)

coadd_input_summary_tract = Table()
coadd_input_summary_tract["tract"] = np.zeros(n_inputs, dtype=np.int32)
coadd_input_summary_tract["patch"] = np.zeros(n_inputs, dtype=np.int32)
coadd_input_summary_tract["visit"] = np.zeros(n_inputs, dtype=np.int64)
coadd_input_summary_tract["detector"] = np.zeros(n_inputs, dtype=np.int32)
coadd_input_summary_tract["weight"] = np.zeros(n_inputs)
coadd_input_summary_tract["goodpix"] = np.zeros(n_inputs, dtype=np.int32)
coadd_input_summary_tract["band"] = np.zeros(n_inputs, dtype="U3")

counter = 0
for (tract, patch, band), detectors in detector_table_dict.items():
ndet = len(detectors)

coadd_input_summary_tract["tract"][counter : counter + ndet] = tract
coadd_input_summary_tract["patch"][counter : counter + ndet] = patch
coadd_input_summary_tract["band"][counter : counter + ndet] = band

coadd_input_summary_tract["visit"][counter : counter + ndet] = detectors["visit"]
coadd_input_summary_tract["detector"][counter : counter + ndet] = detectors["ccd"]
coadd_input_summary_tract["weight"][counter : counter + ndet] = detectors["weight"]
coadd_input_summary_tract["goodpix"][counter : counter + ndet] = detectors["goodpix"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for not volunteering to review earlier. Is goodpix useful here? The cell-based coadd variant will have everything else but that and it'd be good to keep the output agnostic to the flavor of the coadd.


counter += ndet

return lsst.pipe.base.Struct(coadd_input_summary_tract=coadd_input_summary_tract)


class MakeCoaddInputSummaryConnections(
lsst.pipe.base.PipelineTaskConnections,
dimensions=("skymap",),
):
coadd_input_summary_tract_handles = lsst.pipe.base.connectionTypes.Input(
name="coadd_input_summary_tract",
doc="Summary table aggregating coaddInputs from multiple coadds (including "
"all coaddInputs tables in a tract for a single band).",
storageClass="ArrowAstropy",
dimensions=("skymap", "tract", "band"),
deferLoad=True,
multiple=True,
)
coadd_input_summary = lsst.pipe.base.connectionTypes.Output(
name="coadd_input_summary",
doc="Summary table aggregating coaddInputs from all coadds, including all tracts and bands.",
storageClass="ArrowAstropy",
dimensions=("skymap",),
)


class MakeCoaddInputSummaryConfig(
lsst.pipe.base.PipelineTaskConfig,
pipelineConnections=MakeCoaddInputSummaryConnections,
):
pass


class MakeCoaddInputSummary(lsst.pipe.base.PipelineTask):
"""Task to summarize coadd inputs over a full run."""

ConfigClass = MakeCoaddInputSummaryConfig
_DefaultName = "make_coadd_input_summary"

def run(self, *, coadd_input_summary_tract_handles):
"""Run the MakeCoaddInputSummary task.

Parameters
----------
coadd_input_summary_tract_handles : `list`
[`lsst.daf.butler.DeferredDatasetHandle`]
List of summarized coadd inputs to combine.

Returns
-------
result : `lsst.pipe.base.Struct`
Result struct containing:
``coadd_input_summary`` : `astropy.table.Table`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above re: column descriptions.


Notes
-----
The output table contains the the following columns:

tract : `int`
The tract number.
patch : `int`
The patch number.
visit : `int`
The visit number.
detector : `int`
The detector number.
weight : `float`
The weight that was used as an input to the coadd.
goodpix : `int`
The number of pixels from the visit/detector in the patch.
band : `str`
The band for the visit.
"""
self.log.info("Combining %d summarized coadd input catalogs", len(coadd_input_summary_tract_handles))

coadd_input_summary = TableVStack.vstack_handles(coadd_input_summary_tract_handles)

return lsst.pipe.base.Struct(coadd_input_summary=coadd_input_summary)
134 changes: 134 additions & 0 deletions tests/test_make_coadd_input_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# This file is part of drp_tasks.
#
# LSST Data Management System
# This product includes software developed by the
# LSST Project (http://www.lsst.org/).
# See COPYRIGHT file at the top of the source tree.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the LSST License Statement and
# the GNU General Public License along with this program. If not,
# see <https://www.lsstcorp.org/LegalNotices/>.

import unittest

import numpy as np

from lsst.drp.tasks.make_coadd_input_summary import (
MakeCoaddInputSummary,
MakeCoaddInputSummaryTract,
)
from lsst.pipe.base import InMemoryDatasetHandle
from lsst.pipe.tasks.coaddInputRecorder import CoaddInputRecorderTask


class MakeCoaddInputSummaryTestCase(unittest.TestCase):
def setUp(self):
self.recorder_task = CoaddInputRecorderTask(name="recorder")

def _make_coadd_input_handle(self, tract, patch, band, n_input):
coadd_inputs = self.recorder_task.makeCoaddInputs()

coadd_inputs.ccds.resize(n_input)
coadd_inputs.ccds["id"] = np.arange(n_input)
coadd_inputs.ccds["ccd"] = np.arange(n_input)
coadd_inputs.ccds["visit"] = np.arange(100, 100 + n_input)
coadd_inputs.ccds["goodpix"][:] = 1000
coadd_inputs.ccds["weight"][:] = 1.0

handle = InMemoryDatasetHandle(
coadd_inputs,
dataId={"tract": tract, "patch": patch, "band": band},
storageClass="CoaddInputs",
)

return handle

def test_make_coadd_input_summary_tract(self):
handles = [self._make_coadd_input_handle(100, patch, "g", 10) for patch in [0, 10, 20]]

task = MakeCoaddInputSummaryTract()
summary_tract = task.run(coadd_inputs_handles=handles).coadd_input_summary_tract

self.assertEqual(len(summary_tract), 10 * 3)
np.testing.assert_array_equal(summary_tract["tract"], 100)
np.testing.assert_array_equal(summary_tract["band"], "g")

index = 0
for handle in handles:
inputs = handle.get().ccds

np.testing.assert_array_equal(
summary_tract["patch"][index : index + len(inputs)],
handle.dataId["patch"],
)
np.testing.assert_array_equal(
summary_tract["visit"][index : index + len(inputs)],
inputs["visit"],
)
np.testing.assert_array_equal(
summary_tract["detector"][index : index + len(inputs)],
inputs["ccd"],
)
np.testing.assert_array_equal(
summary_tract["goodpix"][index : index + len(inputs)],
inputs["goodpix"],
)
np.testing.assert_array_almost_equal(
summary_tract["weight"][index : index + len(inputs)],
inputs["weight"],
)
index += len(inputs)

def test_make_coadd_input_summary(self):
task1 = MakeCoaddInputSummaryTract()

summary_tract_handles = []

for tract in [100, 200]:
handles = [self._make_coadd_input_handle(tract, patch, "g", 10) for patch in [0, 10, 20]]

summary_tract = task1.run(coadd_inputs_handles=handles).coadd_input_summary_tract

summary_tract_handles.append(
InMemoryDatasetHandle(
summary_tract,
dataId={"tract": tract, "band": "g"},
storageClass="ArrowAstropy",
),
)

task2 = MakeCoaddInputSummary()
summary = task2.run(coadd_input_summary_tract_handles=summary_tract_handles).coadd_input_summary

self.assertEqual(len(summary), 2 * 10 * 3)

index = 0
for handle in summary_tract_handles:
summary_tract = handle.get()

for name in ["tract", "patch", "visit", "detector", "goodpix"]:
np.testing.assert_array_equal(
summary[name][index : index + len(summary_tract)],
summary_tract[name],
)

np.testing.assert_array_almost_equal(
summary["weight"][index : index + len(summary_tract)],
summary_tract["weight"],
)

index += len(summary_tract)


if __name__ == "__main__":
unittest.main()