Skip to content

DM-50987: Add sattle to pipelines #1141

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
60 changes: 60 additions & 0 deletions python/lsst/pipe/tasks/calibrateImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"AllCentroidsFlaggedError"]

import numpy as np
from astropy.time import Time
import requests
import os

import lsst.afw.table as afwTable
import lsst.afw.image as afwImage
Expand Down Expand Up @@ -384,6 +387,35 @@ class CalibrateImageConfig(pipeBase.PipelineTaskConfig, pipelineConnections=Cali
doc="If True, include astrometric errors in the output catalog.",
)

run_sattle = pexConfig.Field(
dtype=bool,
default=False,
doc="If True, sattle service will populate catalog for use in "
"ip_diffim.detectAndMeasure alert verification."
)

sattle_historical = sattle_host = pexConfig.Field(
dtype=bool,
default=False,
doc="If re-running a pipeline that requires sattle, this should be set"
"to True. This will population sattles cache with the historic data"
"closest in time to the exposure.",
)

sattle_host = pexConfig.Field(
dtype=str,
default=os.getenv("SATTLE_HOST"),
doc="The host name for the sattle API.",
optional=True,
)

sattle_port = pexConfig.Field(
dtype=int,
default=os.getenv("SATTLE_PORT"),
doc="Port for the sattle API.",
optional=True,
)

def setDefaults(self):
super().setDefaults()

Expand Down Expand Up @@ -901,6 +933,34 @@ def run(
result.applied_photo_calib = photo_calib
else:
result.applied_photo_calib = None

if self.config.run_sattle:
if not self.config.sattle_host or not self.config.sattle_port:
raise RuntimeError("Sattle filtering is on but service endpoints not set.")

visit_id = result.exposure.getInfo().getVisitInfo().id
visit_date = Time(result.exposure.getInfo().getVisitInfo().getDate().toPython()).jd
exposure_time_days = result.exposure.getInfo().getVisitInfo().getExposureTime() / 86400.0
exposure_end_jd = visit_date + exposure_time_days / 2.0
exposure_start_jd = visit_date - exposure_time_days / 2.0
boresight_ra = result.exposure.getInfo().getVisitInfo().boresightRaDec[
0].asDegrees()
boresight_dec = result.exposure.getInfo().getVisitInfo().boresightRaDec[
1].asDegrees()

r = requests.put(f'{self.config.sattle_host}:{self.config.sattle_port}'
f'/visit_cache', json={"visit_id": visit_id,
"exposure_start_mjd": exposure_start_jd,
"exposure_end_mjd": exposure_end_jd,
"boresight_ra": boresight_ra,
"boresight_dec": boresight_dec,
"historical": self.config.sattle_historical})

if r.status_code != 200:
self.log.warning(f'Sattle cache returned {r.status_code}: {r.text}')
else:
self.log.info('Successfully loaded sattle visit cache')
Comment on lines +941 to +962
Copy link
Member

Choose a reason for hiding this comment

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

This duplicates code with lsst/ip_diffim#418. Unfortunately, I'm not sure where to put it, especially since IIRC pipe_tasks depends on ip_diffim. Maybe @ktlim has an idea?


return result

def _apply_illumination_correction(self, exposure, background_flat, illumination_correction):
Expand Down
61 changes: 61 additions & 0 deletions tests/test_calibrateImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import unittest
from unittest import mock
import tempfile
import json as json_package

import astropy.units as u
from astropy.coordinates import SkyCoord
Expand Down Expand Up @@ -120,6 +121,7 @@ def setUp(self):
# We don't have many test points, so can't match on complicated shapes.
self.config.astrometry.sourceSelector["science"].flags.good = []
self.config.astrometry.matcher.numPointsForShape = 3
self.config.run_sattle = False
# ApFlux has more noise than PsfFlux (the latter unrealistically small
# in this test data), so we need to do magnitude rejection at higher
# sigma, otherwise we can lose otherwise good sources.
Expand Down Expand Up @@ -584,6 +586,65 @@ def test_calibrate_image_illumcorr(self):
self.assertIn(key, result.exposure.metadata)
self.assertEqual(result.exposure.metadata[key], True)

@staticmethod
def _mocked_requests_put(url, json=None):
Copy link
Member

Choose a reason for hiding this comment

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

Same comments as in ip_diffim. If the Sattle access code is factored out, that should also eliminate most of the test redundancy.

data = json

class MockResponse:
def __init__(self, json_data, status_code, text):
self.content = json_package.dumps(json_data)
self.status_code = status_code
self.text = text

def json(self):
return self.json_data

# sattle returns an error code
if data['visit_id'] == 42:
return MockResponse(None, 500, "general error")
elif data['visit_id'] == 99:
return MockResponse(None, 200,
"Success")

return MockResponse(None, 404, "Nothing here")

def test_fail_on_sattle_miconfiguration(self):
"""Test for failure if sattle is requested without appropriate configurations.
"""
self.config.run_sattle = True
calibrate = CalibrateImageTask(config=self.config)
calibrate.astrometry.setRefObjLoader(self.ref_loader)
calibrate.photometry.match.setRefObjLoader(self.ref_loader)
with self.assertRaises(RuntimeError):
calibrate.run(exposures=self.exposure)

@mock.patch('lsst.pipe.tasks.calibrateImage.requests.put', side_effect=_mocked_requests_put)
def test_warn_on_sattle_failure(self, mock_put):
"""Test for a warning when sattle returns status codes other than 200.
"""
self.config.run_sattle = True
self.config.sattle_port = 9999
self.config.sattle_host = 'fake_host'
self.exposure.info.id = 42
calibrate = CalibrateImageTask(config=self.config)
calibrate.astrometry.setRefObjLoader(self.ref_loader)
calibrate.photometry.match.setRefObjLoader(self.ref_loader)
with self.assertWarns(Warning):
calibrate.run(exposures=self.exposure)

@mock.patch('lsst.pipe.tasks.calibrateImage.requests.put', side_effect=_mocked_requests_put)
def test_sattle(self, mock_put):
"""Test that run() returns reasonable values to be butler put.
"""
self.config.run_sattle = True
self.config.sattle_port = 9999
self.config.sattle_host = 'fake_host'
self.exposure.info.id = 99
calibrate = CalibrateImageTask(config=self.config)
calibrate.astrometry.setRefObjLoader(self.ref_loader)
calibrate.photometry.match.setRefObjLoader(self.ref_loader)
calibrate.run(exposures=self.exposure)


class CalibrateImageTaskRunQuantumTests(lsst.utils.tests.TestCase):
"""Tests of ``CalibrateImageTask.runQuantum``, which need a test butler,
Expand Down