Skip to content

Commit 2a8a10c

Browse files
authored
Merge pull request #1141 from lsst/tickets/DM-50987
DM-50987: Add sattle to pipelines
2 parents 60c8b41 + c989a62 commit 2a8a10c

File tree

2 files changed

+89
-1
lines changed

2 files changed

+89
-1
lines changed

python/lsst/pipe/tasks/calibrateImage.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
"AllCentroidsFlaggedError"]
2424

2525
import numpy as np
26+
import requests
27+
import os
2628

2729
import lsst.afw.table as afwTable
2830
import lsst.afw.image as afwImage
29-
from lsst.ip.diffim.utils import evaluateMaskFraction
31+
from lsst.ip.diffim.utils import evaluateMaskFraction, populate_sattle_visit_cache
3032
import lsst.meas.algorithms
3133
import lsst.meas.algorithms.installGaussianPsf
3234
import lsst.meas.algorithms.measureApCorr
@@ -384,6 +386,21 @@ class CalibrateImageConfig(pipeBase.PipelineTaskConfig, pipelineConnections=Cali
384386
doc="If True, include astrometric errors in the output catalog.",
385387
)
386388

389+
run_sattle = pexConfig.Field(
390+
dtype=bool,
391+
default=False,
392+
doc="If True, the sattle service will populate a cache for later use "
393+
"in ip_diffim.detectAndMeasure alert verification."
394+
)
395+
396+
sattle_historical = pexConfig.Field(
397+
dtype=bool,
398+
default=False,
399+
doc="If re-running a pipeline that requires sattle, this should be set "
400+
"to True. This will populate sattle's cache with the historic data "
401+
"closest in time to the exposure.",
402+
)
403+
387404
def setDefaults(self):
388405
super().setDefaults()
389406

@@ -565,6 +582,11 @@ def validate(self):
565582
"doApplyFlatBackgroundRatio=True if do_illumination_correction=True."
566583
)
567584

585+
if self.run_sattle:
586+
if not os.getenv("SATTLE_URI_BASE"):
587+
raise pexConfig.FieldValidationError(CalibrateImageConfig.run_sattle, self,
588+
"Sattle requested but URI environment variable not set.")
589+
568590

569591
class CalibrateImageTask(pipeBase.PipelineTask):
570592
"""Compute the PSF, aperture corrections, astrometric and photometric
@@ -901,6 +923,17 @@ def run(
901923
result.applied_photo_calib = photo_calib
902924
else:
903925
result.applied_photo_calib = None
926+
927+
if self.config.run_sattle:
928+
# send boresight and timing information to sattle so the cache
929+
# is populated by the time we reach ip_diffim detectAndMeasure.
930+
try:
931+
populate_sattle_visit_cache(result.exposure.getInfo().getVisitInfo(),
932+
historical=self.config.sattle_historical)
933+
self.log.info('Successfully triggered load of sattle visit cache')
934+
except requests.exceptions.HTTPError:
935+
self.log.exception("Sattle visit cache update failed; continuing with image processing")
936+
904937
return result
905938

906939
def _apply_illumination_correction(self, exposure, background_flat, illumination_correction):

tests/test_calibrateImage.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import copy
2929
import numpy as np
3030
import esutil
31+
import os
32+
import requests
3133

3234
import lsst.afw.image as afwImage
3335
import lsst.afw.math as afwMath
@@ -44,6 +46,7 @@
4446
import lsst.pipe.base.testUtils
4547
from lsst.pipe.tasks.calibrateImage import CalibrateImageTask, \
4648
NoPsfStarsToStarsMatchError, AllCentroidsFlaggedError
49+
import lsst.pex.config as pexConfig
4750
import lsst.utils.tests
4851

4952

@@ -120,6 +123,7 @@ def setUp(self):
120123
# We don't have many test points, so can't match on complicated shapes.
121124
self.config.astrometry.sourceSelector["science"].flags.good = []
122125
self.config.astrometry.matcher.numPointsForShape = 3
126+
self.config.run_sattle = False
123127
# ApFlux has more noise than PsfFlux (the latter unrealistically small
124128
# in this test data), so we need to do magnitude rejection at higher
125129
# sigma, otherwise we can lose otherwise good sources.
@@ -584,6 +588,42 @@ def test_calibrate_image_illumcorr(self):
584588
self.assertIn(key, result.exposure.metadata)
585589
self.assertEqual(result.exposure.metadata[key], True)
586590

591+
@mock.patch.dict(os.environ, {"SATTLE_URI_BASE": ""})
592+
def test_fail_on_sattle_miconfiguration(self):
593+
"""Test for failure if sattle is requested without appropriate configurations.
594+
"""
595+
self.config.run_sattle = True
596+
with self.assertRaises(pexConfig.FieldValidationError):
597+
CalibrateImageTask(config=self.config)
598+
599+
@mock.patch.dict(os.environ, {"SATTLE_URI_BASE": "fake_host:1234"})
600+
def test_continue_on_sattle_failure(self):
601+
"""Processing should continue when sattle returns status codes other than 200.
602+
"""
603+
response = MockResponse({}, 500, "internal sattle error")
604+
605+
self.config.run_sattle = True
606+
calibrate = CalibrateImageTask(config=self.config)
607+
calibrate.astrometry.setRefObjLoader(self.ref_loader)
608+
calibrate.photometry.match.setRefObjLoader(self.ref_loader)
609+
with mock.patch('requests.put', return_value=response) as mock_put:
610+
calibrate.run(exposures=self.exposure)
611+
mock_put.assert_called_once()
612+
613+
@mock.patch.dict(os.environ, {"SATTLE_URI_BASE": "fake_host:1234"})
614+
def test_sattle(self):
615+
"""Test for successful completion when sattle call returns successfully.
616+
"""
617+
response = MockResponse({}, 200, "success")
618+
619+
self.config.run_sattle = True
620+
calibrate = CalibrateImageTask(config=self.config)
621+
calibrate.astrometry.setRefObjLoader(self.ref_loader)
622+
calibrate.photometry.match.setRefObjLoader(self.ref_loader)
623+
with mock.patch('requests.put', return_value=response) as mock_put:
624+
calibrate.run(exposures=self.exposure)
625+
mock_put.assert_called_once()
626+
587627

588628
class CalibrateImageTaskRunQuantumTests(lsst.utils.tests.TestCase):
589629
"""Tests of ``CalibrateImageTask.runQuantum``, which need a test butler,
@@ -955,6 +995,21 @@ def mock_run(
955995
self.butler.get("initial_stars_footprints_detector", self.visit_id)
956996

957997

998+
class MockResponse:
999+
"""Provide a mock for requests.put calls"""
1000+
def __init__(self, json_data, status_code, text):
1001+
self.json_data = json_data
1002+
self.status_code = status_code
1003+
self.text = text
1004+
1005+
def json(self):
1006+
return self.json_data
1007+
1008+
def raise_for_status(self):
1009+
if self.status_code != 200:
1010+
raise requests.exceptions.HTTPError
1011+
1012+
9581013
def setup_module(module):
9591014
lsst.utils.tests.init()
9601015

0 commit comments

Comments
 (0)