Skip to content

Commit b111981

Browse files
authored
Merge pull request #988 from lsst/tickets/DM-46708
DM-46708: un-deprecate MakeWarpTask
2 parents fca6ebe + 6522181 commit b111981

File tree

3 files changed

+292
-12
lines changed

3 files changed

+292
-12
lines changed

python/lsst/pipe/tasks/makeWarp.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import lsst.pipe.base.connectionTypes as connectionTypes
3232
import lsst.utils as utils
3333
import lsst.geom
34-
from deprecated.sphinx import deprecated
3534
from lsst.daf.butler import DeferredDatasetHandle
3635
from lsst.meas.base import DetectorVisitIdGeneratorConfig
3736
from lsst.meas.algorithms import CoaddPsf, CoaddPsfConfig, GaussianPsfFactory
@@ -109,8 +108,6 @@ def __init__(self, *, config=None):
109108
del self.psfMatched
110109

111110

112-
@deprecated(reason="The Task corresponding to this Config is no longer in use. Will be removed after v28.",
113-
version="v28.0", category=FutureWarning)
114111
class MakeWarpConfig(pipeBase.PipelineTaskConfig, CoaddBaseTask.ConfigClass,
115112
pipelineConnections=MakeWarpConnections):
116113
"""Config for MakeWarpTask."""
@@ -197,9 +194,6 @@ def setDefaults(self):
197194
self.warpAndPsfMatch.psfMatch.kernel.active.kernelSize = self.matchingKernelSize
198195

199196

200-
@deprecated(reason="The MakeWarpTask is replaced by MakeDirectWarpTask and MakePsfMatchedWarpTask. "
201-
"This Task will be removed after v28.",
202-
version="v28.0", category=FutureWarning)
203197
class MakeWarpTask(CoaddBaseTask):
204198
"""Warp and optionally PSF-Match calexps onto an a common projection.
205199

python/lsst/pipe/tasks/warpAndPsfMatch.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,10 @@
2626
import lsst.geom as geom
2727
import lsst.afw.geom as afwGeom
2828
import lsst.pipe.base as pipeBase
29-
from deprecated.sphinx import deprecated
3029
from lsst.ip.diffim import ModelPsfMatchTask
3130
from lsst.meas.algorithms import WarpedPsf
3231

3332

34-
@deprecated(reason="The Task corresponding to this Config is no longer in use. Will be removed after v28.",
35-
version="v28.0", category=FutureWarning)
3633
class WarpAndPsfMatchConfig(pexConfig.Config):
3734
"""Config for WarpAndPsfMatchTask
3835
"""
@@ -46,9 +43,6 @@ class WarpAndPsfMatchConfig(pexConfig.Config):
4643
)
4744

4845

49-
@deprecated(reason="The WarpAndPsfMatchTask is no longer in use. Will be removed after v28. "
50-
"Use MakeDirectWarp and MakePsfMatchedWarp instead.",
51-
version="v28.0", category=FutureWarning)
5246
class WarpAndPsfMatchTask(pipeBase.Task):
5347
"""A task to warp and PSF-match an exposure
5448
"""

tests/test_makeWarp.py

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# This file is part of pipe_tasks.
2+
#
3+
# Developed for the LSST Data Management System.
4+
# This product includes software developed by the LSST Project
5+
# (https://www.lsst.org).
6+
# See the COPYRIGHT file at the top-level directory of this distribution
7+
# for details of code ownership.
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation, either version 3 of the License, or
12+
# (at your option) any later version.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
21+
22+
import unittest
23+
24+
from typing import Self, Type
25+
26+
import numpy as np
27+
28+
import lsst.utils.tests
29+
30+
import lsst.afw.image
31+
from lsst.daf.butler import DataCoordinate, DimensionUniverse
32+
from lsst.pipe.base import InMemoryDatasetHandle
33+
from lsst.pipe.tasks.make_direct_warp import MakeDirectWarpTask
34+
from lsst.pipe.tasks.makeWarp import (MakeWarpTask, MakeWarpConfig)
35+
from lsst.pipe.tasks.coaddBase import makeSkyInfo
36+
import lsst.skymap as skyMap
37+
from lsst.afw.detection import GaussianPsf
38+
import lsst.afw.cameraGeom.testUtils
39+
40+
41+
class MakeWarpTestCase(lsst.utils.tests.TestCase):
42+
def setUp(self):
43+
np.random.seed(12345)
44+
45+
self.config = MakeWarpConfig()
46+
47+
meanCalibration = 1e-4
48+
calibrationErr = 1e-5
49+
self.exposurePhotoCalib = lsst.afw.image.PhotoCalib(meanCalibration, calibrationErr)
50+
# An external photoCalib calibration to return
51+
self.externalPhotoCalib = lsst.afw.image.PhotoCalib(1e-6, 1e-8)
52+
53+
crpix = lsst.geom.Point2D(0, 0)
54+
crval = lsst.geom.SpherePoint(0, 45, lsst.geom.degrees)
55+
cdMatrix = lsst.afw.geom.makeCdMatrix(scale=1.0*lsst.geom.arcseconds)
56+
self.skyWcs = lsst.afw.geom.makeSkyWcs(crpix, crval, cdMatrix)
57+
externalCdMatrix = lsst.afw.geom.makeCdMatrix(scale=0.9*lsst.geom.arcseconds)
58+
# An external skyWcs to return
59+
self.externalSkyWcs = lsst.afw.geom.makeSkyWcs(crpix, crval, externalCdMatrix)
60+
61+
self.exposure = lsst.afw.image.ExposureF(100, 150)
62+
self.exposure.maskedImage.image.array = np.random.random((150, 100)).astype(np.float32) * 1000
63+
self.exposure.maskedImage.variance.array = np.random.random((150, 100)).astype(np.float32)
64+
# mask at least one pixel
65+
self.exposure.maskedImage.mask[5, 5] = 3
66+
# set the PhotoCalib and Wcs objects of this exposure.
67+
self.exposure.setPhotoCalib(lsst.afw.image.PhotoCalib(meanCalibration, calibrationErr))
68+
self.exposure.setWcs(self.skyWcs)
69+
self.exposure.setPsf(GaussianPsf(5, 5, 2.5))
70+
self.exposure.setFilter(lsst.afw.image.FilterLabel(physical="fakeFilter", band="fake"))
71+
72+
self.visit = 100
73+
self.detector = 5
74+
detectorName = f"detector {self.detector}"
75+
detector = lsst.afw.cameraGeom.testUtils.DetectorWrapper(name=detectorName, id=self.detector).detector
76+
self.exposure.setDetector(detector)
77+
78+
simpleMapConfig = skyMap.discreteSkyMap.DiscreteSkyMapConfig()
79+
simpleMapConfig.raList = [crval.getRa().asDegrees()]
80+
simpleMapConfig.decList = [crval.getDec().asDegrees()]
81+
simpleMapConfig.radiusList = [0.1]
82+
83+
self.simpleMap = skyMap.DiscreteSkyMap(simpleMapConfig)
84+
self.tractId = 0
85+
self.patchId = self.simpleMap[0].findPatch(crval).sequential_index
86+
self.skyInfo = makeSkyInfo(self.simpleMap, self.tractId, self.patchId)
87+
88+
@classmethod
89+
def generate_data_id(
90+
cls: Type[Self],
91+
*,
92+
tract: int = 9813,
93+
patch: int = 42,
94+
band: str = "r",
95+
detector_id: int = 9,
96+
visit_id: int = 1234,
97+
detector_max: int = 109,
98+
visit_max: int = 10000
99+
) -> DataCoordinate:
100+
"""Generate a DataCoordinate instance to use as data_id.
101+
102+
Parameters
103+
----------
104+
tract : `int`, optional
105+
Tract ID for the data_id
106+
patch : `int`, optional
107+
Patch ID for the data_id
108+
band : `str`, optional
109+
Band for the data_id
110+
detector_id : `int`, optional
111+
Detector ID for the data_id
112+
visit_id : `int`, optional
113+
Visit ID for the data_id
114+
detector_max : `int`, optional
115+
Maximum detector ID for the data_id
116+
visit_max : `int`, optional
117+
Maximum visit ID for the data_id
118+
119+
Returns
120+
-------
121+
data_id : `lsst.daf.butler.DataCoordinate`
122+
An expanded data_id instance.
123+
"""
124+
universe = DimensionUniverse()
125+
126+
instrument = universe["instrument"]
127+
instrument_record = instrument.RecordClass(
128+
name="DummyCam",
129+
class_name="lsst.obs.base.instrument_tests.DummyCam",
130+
detector_max=detector_max,
131+
visit_max=visit_max,
132+
)
133+
134+
skymap = universe["skymap"]
135+
skymap_record = skymap.RecordClass(name="test_skymap")
136+
137+
band_element = universe["band"]
138+
band_record = band_element.RecordClass(name=band)
139+
140+
visit = universe["visit"]
141+
visit_record = visit.RecordClass(id=visit_id, instrument="test")
142+
143+
detector = universe["detector"]
144+
detector_record = detector.RecordClass(id=detector_id, instrument="test")
145+
146+
physical_filter = universe["physical_filter"]
147+
physical_filter_record = physical_filter.RecordClass(name=band, instrument="test", band=band)
148+
149+
patch_element = universe["patch"]
150+
patch_record = patch_element.RecordClass(
151+
skymap="test_skymap", tract=tract, patch=patch,
152+
)
153+
154+
if "day_obs" in universe:
155+
day_obs_element = universe["day_obs"]
156+
day_obs_record = day_obs_element.RecordClass(id=20240201, instrument="test")
157+
else:
158+
day_obs_record = None
159+
160+
# A dictionary with all the relevant records.
161+
record = {
162+
"instrument": instrument_record,
163+
"visit": visit_record,
164+
"detector": detector_record,
165+
"patch": patch_record,
166+
"tract": 9813,
167+
"band": band_record.name,
168+
"skymap": skymap_record.name,
169+
"physical_filter": physical_filter_record,
170+
}
171+
172+
if day_obs_record:
173+
record["day_obs"] = day_obs_record
174+
175+
# A dictionary with all the relevant recordIds.
176+
record_id = record.copy()
177+
for key in ("visit", "detector"):
178+
record_id[key] = record_id[key].id
179+
180+
# TODO: Catching mypy failures on Github Actions should be made easier,
181+
# perhaps in DM-36873. Igroring these for now.
182+
data_id = DataCoordinate.standardize(record_id, universe=universe)
183+
return data_id.expanded(record)
184+
185+
def test_makeWarp(self):
186+
"""Test basic MakeWarpTask."""
187+
makeWarp = MakeWarpTask(config=self.config)
188+
189+
result = makeWarp.run(
190+
calExpList=[self.exposure],
191+
ccdIdList=[self.detector],
192+
skyInfo=self.skyInfo,
193+
visitId=self.visit,
194+
dataIdList=[{'visit': self.visit, 'detector': self.detector}],
195+
)
196+
197+
warp = result.exposures['direct']
198+
199+
# Ensure we got an exposure out
200+
self.assertIsInstance(warp, lsst.afw.image.ExposureF)
201+
# Ensure the warp has valid pixels
202+
self.assertGreater(np.isfinite(warp.image.array.ravel()).sum(), 0)
203+
# Ensure the warp has the correct WCS
204+
self.assertEqual(warp.getWcs(), self.skyInfo.wcs)
205+
206+
def test_psfMatched(self):
207+
"""Test that the direct warp is independent of makePsfMatched config.
208+
"""
209+
self.config.makePsfMatched = False
210+
task1 = MakeWarpTask(config=self.config)
211+
self.config.makePsfMatched = True
212+
task2 = MakeWarpTask(config=self.config)
213+
214+
result1 = task1.run(
215+
calExpList=[self.exposure],
216+
ccdIdList=[self.detector],
217+
skyInfo=self.skyInfo,
218+
visitId=self.visit,
219+
dataIdList=[{'visit': self.visit, 'detector': self.detector}],
220+
)
221+
222+
result2 = task2.run(
223+
calExpList=[self.exposure],
224+
ccdIdList=[self.detector],
225+
skyInfo=self.skyInfo,
226+
visitId=self.visit,
227+
dataIdList=[{'visit': self.visit, 'detector': self.detector}],
228+
)
229+
230+
# Ensure we got a valid exposure out
231+
warp = result2.exposures['psfMatched']
232+
self.assertIsInstance(warp, lsst.afw.image.ExposureF)
233+
self.assertGreater(np.isfinite(warp.image.array.ravel()).sum(), 0)
234+
# Check that the direct images are the same.
235+
self.assertMaskedImagesEqual(
236+
result1.exposures['direct'].maskedImage,
237+
result2.exposures['direct'].maskedImage
238+
)
239+
240+
def test_compare_warps(self):
241+
"""Test that the warp from MakeWarpTask and MakeDirectWarpTask agree.
242+
"""
243+
dataIdList = [{'visit_id': self.visit, 'detector_id': self.detector, "band": "i"}]
244+
245+
dataRefs = [
246+
InMemoryDatasetHandle(self.exposure, dataId=self.generate_data_id(**dataId))
247+
for dataId in dataIdList
248+
]
249+
inputs = {"calexp_list": dataRefs}
250+
251+
for dataId in dataIdList:
252+
dataId["detector"] = dataId.pop("detector_id")
253+
dataId["visit"] = dataId.pop("visit_id")
254+
255+
self.config.makePsfMatched = True
256+
makeWarp = MakeWarpTask(config=self.config)
257+
result0 = makeWarp.run(
258+
calExpList=[self.exposure],
259+
ccdIdList=[self.detector],
260+
skyInfo=self.skyInfo,
261+
visitId=self.visit,
262+
dataIdList=dataIdList,
263+
)
264+
265+
config = MakeDirectWarpTask.ConfigClass()
266+
config.doPreWarpInterpolation = False
267+
config.doSelectPreWarp = False
268+
config.useVisitSummaryPsf = False
269+
task = MakeDirectWarpTask(config=config)
270+
result1 = task.run(
271+
inputs,
272+
sky_info=self.skyInfo,
273+
visit_summary=None
274+
)
275+
276+
warp0 = result0.exposures["direct"]
277+
warp1 = result1.warp[warp0.getBBox()]
278+
279+
self.assertMaskedImagesAlmostEqual(warp0.maskedImage, warp1.maskedImage, rtol=3e-7, atol=6e-6)
280+
281+
282+
def setup_module(module):
283+
lsst.utils.tests.init()
284+
285+
286+
class MatchMemoryTestCase(lsst.utils.tests.MemoryTestCase):
287+
pass
288+
289+
290+
if __name__ == "__main__":
291+
lsst.utils.tests.init()
292+
unittest.main()

0 commit comments

Comments
 (0)