Skip to content

Commit 55db76b

Browse files
committed
Respond to review.
1 parent 2dcc1d8 commit 55db76b

File tree

4 files changed

+219
-212
lines changed

4 files changed

+219
-212
lines changed

python/lsst/ip/diffim/detectAndMeasure.py

Lines changed: 66 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121

2222
import numpy as np
2323
import requests
24-
import json
2524
import os
26-
from astropy.time import Time
2725

2826
import lsst.afw.detection as afwDetection
2927
import lsst.afw.image as afwImage
@@ -32,6 +30,7 @@
3230
import lsst.daf.base as dafBase
3331
import lsst.geom
3432
from lsst.ip.diffim.utils import evaluateMaskFraction, computeDifferenceImageMetrics
33+
from lsst.ip.diffim.utils import populate_sattle_visit_cache
3534
from lsst.meas.algorithms import SkyObjectsTask, SourceDetectionTask, SetPrimaryFlagsTask, MaskStreaksTask
3635
from lsst.meas.base import ForcedMeasurementTask, ApplyApCorrTask, DetectorVisitIdGeneratorConfig
3736
import lsst.meas.deblender
@@ -313,18 +312,6 @@ class DetectAndMeasureConfig(pipeBase.PipelineTaskConfig,
313312
doc="If true, dia source bounding boxes will be sent for verification"
314313
"to the sattle service."
315314
)
316-
sattle_host = pexConfig.Field(
317-
dtype=str,
318-
default=os.getenv("SATTLE_HOST"),
319-
doc="Host address for sattle service.",
320-
optional=True
321-
)
322-
sattle_port = pexConfig.Field(
323-
dtype=str,
324-
default=os.getenv("SATTLE_PORT"),
325-
doc="Port to connect to sattle.",
326-
optional=True
327-
)
328315
sattle_historical = pexConfig.Field(
329316
dtype=bool,
330317
default=False,
@@ -403,6 +390,14 @@ def setDefaults(self):
403390
"STREAK", "INJECTED", "INJECTED_TEMPLATE"]
404391
self.skySources.avoidMask = ["DETECTED", "DETECTED_NEGATIVE", "BAD", "NO_DATA", "EDGE"]
405392

393+
def validate(self):
394+
super().validate()
395+
396+
if self.run_sattle:
397+
if not os.getenv("SATTLE_URI_BASE"):
398+
raise pexConfig.FieldValidationError(DetectAndMeasureConfig.run_sattle, self,
399+
"Sattle requested but URI environment variable not set.")
400+
406401

407402
class DetectAndMeasureTask(lsst.pipe.base.PipelineTask):
408403
"""Detect and measure sources on a difference image.
@@ -721,8 +716,6 @@ def processResults(self, science, matchedTemplate, difference, sources, idFactor
721716
diaSources = self._removeBadSources(initialDiaSources)
722717

723718
if self.config.run_sattle:
724-
if not self.config.sattle_host or not self.config.sattle_port:
725-
raise RuntimeError("Sattle filtering is on but service endpoints not set.")
726719
diaSources = self.filterSatellites(diaSources, science)
727720

728721
if self.config.doForcedMeasurement:
@@ -982,99 +975,80 @@ def calculateMetrics(self, science, difference, diaSources, kernelSources):
982975
raise BadSubtractionError(ratio=metrics.differenceFootprintRatioStdev,
983976
threshold=self.config.badSubtractionVariationThreshold)
984977

985-
def filterSatellites(self, diaSources, science):
986-
987-
wcs = science.getWcs()
988-
nbbox = []
978+
def getSattleDiaSourceAllowlist(self, diaSources, science):
979+
"""Query the sattle service and determine which diaSources are allowed.
989980
990-
for source in diaSources:
991-
fp = source.getFootprint()
992-
source_bbox = fp.getBBox()
981+
Parameters
982+
----------
983+
diaSources : `lsst.afw.table.SourceCatalog`
984+
The catalog of detected sources.
985+
science : `lsst.afw.image.ExposureF`
986+
Science exposure that was subtracted.
993987
994-
corners = [wcs.pixelToSky(source_bbox.beginX, source_bbox.beginY),
995-
wcs.pixelToSky(source_bbox.beginX, source_bbox.endY),
996-
wcs.pixelToSky(source_bbox.endX, source_bbox.endY),
997-
wcs.pixelToSky(source_bbox.endX, source_bbox.beginY)]
988+
Returns
989+
----------
990+
allow_list : `list` of `int`
991+
diaSourceIds of diaSources that can be made public.
998992
999-
tmp = []
1000-
for c, corner in enumerate(corners):
1001-
tmp.append([corner.getRa().asDegrees(), corner.getDec().asDegrees()])
1002-
nbbox.append(tmp)
993+
Raises
994+
------
995+
requests.HTTPError
996+
Raised if sattle call does not return success.
997+
"""
1003998

1004-
detector_id = science.getDetector().getId()
1005-
visit_id = science.getInfo().getVisitInfo().getId()
999+
wcs = science.getWcs()
1000+
visit_info = science.getInfo().getVisitInfo()
1001+
visit_id = visit_info.getId()
1002+
sattle_uri_base = os.getenv('SATTLE_URI_BASE')
10061003

10071004
dia_sources_json = []
1008-
for i, source in enumerate(diaSources):
1009-
dia_sources_json.append(
1010-
{"diasource_id": source['id'], "bbox": nbbox[i]})
1011-
1012-
sattle_output = requests.put(
1013-
f'{self.config.sattle_host}:{self.config.sattle_port}/diasource_allow_list',
1014-
json={"visit_id": visit_id, "detector_id": detector_id, "diasources": dia_sources_json})
1015-
1016-
if sattle_output.status_code != 200:
1017-
# Check if the cache is missing the visit and retry once
1018-
if sattle_output.status_code == 404:
1019-
try:
1020-
self.log.info('Visit not found in sattle cache, re-sending')
1021-
1022-
visit_id = science.getInfo().getVisitInfo().id
1023-
1024-
visit_mjd = Time(
1025-
science.getInfo().getVisitInfo().getDate().toPython()).mjd
1026-
1027-
exposure_time_days = science.getInfo().getVisitInfo().getExposureTime() / 86400.0
1028-
exposure_end_mjd = visit_mjd + exposure_time_days / 2.0
1029-
exposure_start_mjd = visit_mjd - exposure_time_days / 2.0
1030-
1031-
boresight_ra = science.getInfo().getVisitInfo().boresightRaDec[0].asDegrees()
1032-
boresight_dec = science.getInfo().getVisitInfo().boresightRaDec[1].asDegrees()
1005+
for source in diaSources:
1006+
source_bbox = source.getFootprint().getBBox()
1007+
corners = [wcs.pixelToSky(lsst.geom.Point2D(c)) for c in source_bbox.getCorners()]
1008+
bbox_radec = [[pt.getRa().asDegrees(), pt.getDec().asDegrees()] for pt in corners]
1009+
dia_sources_json.append({"diasource_id": source["id"], "bbox": bbox_radec})
10331010

1034-
r = requests.put(
1035-
f'{self.config.sattle_host}:{self.config.sattle_port}/visit_cache',
1036-
json={"visit_id": visit_id,
1037-
"exposure_start_mjd": exposure_start_mjd,
1038-
"exposure_end_mjd": exposure_end_mjd,
1039-
"boresight_ra": boresight_ra,
1040-
"boresight_dec": boresight_dec,
1041-
"historical": self.config.sattle_historical})
1011+
payload = {"visit_id": visit_id, "detector_id": science.getDetector(), "diasources": dia_sources_json,
1012+
"historical": self.config.sattle_historical}
10421013

1043-
if r.status_code != 200:
1044-
raise RuntimeError(r.text)
1014+
sattle_output = requests.put(f'{sattle_uri_base}/diasource_allow_list',
1015+
json=payload)
10451016

1046-
sattle_output = requests.put(
1047-
f'{self.config.sattle_host}:{self.config.sattle_port}/'
1048-
'diasource_allow_list',
1049-
json={"visit_id": visit_id, "detector_id": detector_id,
1050-
"diasources": dia_sources_json})
1017+
# retry once if visit cache is not populated
1018+
if sattle_output.status_code == 404:
1019+
self.log.warning(f'Visit {visit_id} not found in sattle cache, re-sending')
1020+
populate_sattle_visit_cache(visit_info, historical=self.config.sattle_historical)
1021+
sattle_output = requests.put(f'{sattle_uri_base}/diasource_allow_list', json=payload)
10511022

1052-
# fail on any non-success
1053-
if sattle_output.status_code != 200:
1054-
raise RuntimeError(sattle_output.text)
1023+
sattle_output.raise_for_status()
10551024

1056-
except (requests.RequestException, ConnectionError) as e:
1057-
raise RuntimeError(sattle_output.text) from e
1025+
return sattle_output.json()['allow_list']
10581026

1059-
else:
1060-
raise RuntimeError(sattle_output.text)
1027+
def filterSatellites(self, diaSources, science):
1028+
"""Remove diaSources overlapping predicted satellite positions.
10611029
1062-
sattle_output_array = json.loads(sattle_output.content)
1030+
Parameters
1031+
----------
1032+
diaSources : `lsst.afw.table.SourceCatalog`
1033+
The catalog of detected sources.
1034+
science : `lsst.afw.image.ExposureF`
1035+
Science exposure that was subtracted.
10631036
1064-
if sattle_output_array['allow_list'] == []:
1065-
self.log.warning('Sattle output array is empty, all sources removed')
1066-
diaSources = diaSources[0:0].copy(deep=True)
1067-
else:
1037+
Returns
1038+
----------
1039+
filterdDiaSources : `lsst.afw.table.SourceCatalog`
1040+
Filtered catalog of diaSources
1041+
"""
10681042

1069-
allowed_ids = []
1070-
for source in diaSources:
1071-
if source['id'] in sattle_output_array['allow_list']:
1072-
allowed_ids.append(True)
1073-
else:
1074-
allowed_ids.append(False)
1043+
allow_list = self.getSattleDiaSourceAllowlist(diaSources, science)
10751044

1045+
if len(allow_list):
1046+
allow_set = set(allow_list)
1047+
allowed_ids = [source['id'] in allow_set for source in diaSources]
10761048
diaSources = diaSources[np.array(allowed_ids)].copy(deep=True)
1077-
1049+
else:
1050+
self.log.warning('Sattle allowlist is empty, all diaSources removed')
1051+
diaSources = diaSources[0:0].copy(deep=True)
10781052
return diaSources
10791053

10801054
def _runStreakMasking(self, difference):

python/lsst/ip/diffim/utils.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
import itertools
3030
import numpy as np
31+
import os
32+
import requests
3133
import lsst.geom as geom
3234
import lsst.afw.detection as afwDetection
3335
import lsst.afw.image as afwImage
@@ -415,3 +417,40 @@ def footprint_mean(sources, sky=0):
415417
differenceFootprintSkyRatioMean=sky_mean,
416418
differenceFootprintSkyRatioStdev=sky_std,
417419
)
420+
421+
422+
def populate_sattle_visit_cache(visit_info, historical=False):
423+
"""Populate a cache of predicted satellite positions in the sattle service.
424+
425+
Parameters
426+
----------
427+
visit_info: `lsst.afw.table.ExposureRecord.visitInfo`
428+
Visit info for the science exposure being processed.
429+
historical: `bool`
430+
Set to True if observations are older than the current day.
431+
432+
Raises
433+
------
434+
requests.HTTPError
435+
Raised if sattle call does not return success.
436+
"""
437+
438+
visit_mjd = visit_info.getDate().toAstropy().mjd
439+
440+
exposure_time_days = visit_info.getExposureTime() / 86400.0
441+
exposure_end_mjd = visit_mjd + exposure_time_days / 2.0
442+
exposure_start_mjd = visit_mjd - exposure_time_days / 2.0
443+
444+
boresight_ra = visit_info.boresightRaDec.getRa().asDegrees()
445+
boresight_dec = visit_info.boresightRaDec.getDec().asDegrees()
446+
447+
r = requests.put(
448+
f'{os.getenv("SATTLE_URI_BASE")}/visit_cache',
449+
json={"visit_id": visit_info.getId(),
450+
"exposure_start_mjd": exposure_start_mjd,
451+
"exposure_end_mjd": exposure_end_mjd,
452+
"boresight_ra": boresight_ra,
453+
"boresight_dec": boresight_dec,
454+
"historical": historical})
455+
456+
r.raise_for_status()

0 commit comments

Comments
 (0)