Skip to content

Commit 9dd34a9

Browse files
committed
Add hostless detection science module for Rubin
1 parent cbabb72 commit 9dd34a9

File tree

5 files changed

+206
-2
lines changed

5 files changed

+206
-2
lines changed

fink_science/rubin/hostless_detection/__init__.py

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2024-2025 AstroLab Software
2+
# Author: Rupesh Durgesh
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""Implementation of the paper: ELEPHANT: ExtragaLactic alErt Pipeline for Hostless AstroNomical Transients https://arxiv.org/abs/2404.18165"""
16+
17+
import io
18+
19+
from astropy.io import fits
20+
import numpy as np
21+
22+
23+
def read_cutout_stamp(fits_bytes: bytes) -> np.ndarray:
24+
"""
25+
Reads Rubin cutout stamps
26+
27+
Parameters
28+
----------
29+
fits_bytes
30+
input byte string
31+
"""
32+
fits_buffer = io.BytesIO(fits_bytes)
33+
with fits.open(fits_buffer) as hdulist:
34+
return hdulist[0].data
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright 2020-2025 AstroLab Software
2+
# Author: Rupesh Durgesh
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from line_profiler import profile
17+
import os
18+
19+
import pandas as pd
20+
import pyspark.sql.functions as F
21+
from pyspark.sql.types import ArrayType, FloatType
22+
23+
from fink_science.rubin.hostless_detection.run_pipeline import (
24+
HostLessExtragalacticRubin,
25+
)
26+
from fink_science.ztf.hostless_detection.pipeline_utils import load_json
27+
from fink_science.tester import spark_unit_tests
28+
29+
CONFIGS_BASE = load_json("fink_science/ztf/hostless_detection/config_base.json")
30+
CONFIGS = load_json("fink_science/ztf/hostless_detection/config.json")
31+
CONFIGS.update(CONFIGS_BASE)
32+
33+
34+
@F.pandas_udf(ArrayType(FloatType()))
35+
@profile
36+
def run_potential_hostless(
37+
cutoutScience: pd.Series, cutoutTemplate: pd.Series
38+
) -> pd.Series:
39+
"""Runs potential hostless candidate detection for Rubin without any filtering
40+
41+
Parameters
42+
----------
43+
cutoutScience: pd.Series
44+
science stamp images
45+
cutoutTemplate: pd.Series
46+
template stamp images
47+
48+
Returns
49+
-------
50+
pd.Series
51+
Scores (array of 2 floats) for being hostless
52+
53+
References
54+
----------
55+
1. ELEPHANT: ExtragaLactic alErt Pipeline for Hostless AstroNomical
56+
Transients
57+
https://arxiv.org/abs/2404.18165
58+
59+
Examples
60+
--------
61+
>>> df = spark.read.format('parquet').load(rubin_alert_sample)
62+
>>> df.count()
63+
50
64+
65+
# Add a new column
66+
>>> df = df.withColumn('kstest_static',
67+
... run_potential_hostless(
68+
... df["cutoutScience"],
69+
... df["cutoutTemplate"]))
70+
>>> df.filter(df.kstest_static[0] >= 0).count()
71+
3
72+
"""
73+
kstest_results = []
74+
hostless_science_class = HostLessExtragalacticRubin(CONFIGS_BASE)
75+
76+
for index in range(cutoutScience.shape[0]):
77+
science_stamp = cutoutScience[index]
78+
template_stamp = cutoutTemplate[index]
79+
kstest_science, kstest_template = (
80+
hostless_science_class.process_candidate_fink_rubin(
81+
science_stamp, template_stamp
82+
)
83+
)
84+
kstest_results.append([kstest_science, kstest_template])
85+
return pd.Series(kstest_results)
86+
87+
88+
if __name__ == "__main__":
89+
globs = globals()
90+
path = os.path.dirname(__file__)
91+
92+
rubin_alert_sample = "./fink_science/data/alerts/or4_lsst7.1"
93+
globs["sample_file"] = rubin_alert_sample
94+
spark_unit_tests(globs)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Copyright 2024-2025 AstroLab Software
2+
# Author: Rupesh Durgesh
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
"""Implementation of the paper: ELEPHANT: ExtragaLactic alErt Pipeline for Hostless AstroNomical Transients https://arxiv.org/abs/2404.18165"""
16+
17+
from typing import Tuple
18+
19+
from fink_science.ztf.hostless_detection.pipeline_utils import (
20+
run_hostless_detection_with_clipped_data,
21+
run_powerspectrum_analysis,
22+
)
23+
from fink_science.ztf.hostless_detection.run_pipeline import HostLessExtragalactic
24+
from fink_science.rubin.hostless_detection.pipeline_utils import read_cutout_stamp
25+
26+
27+
class HostLessExtragalacticRubin(HostLessExtragalactic):
28+
"""
29+
Detects potential hostless candidates for extragalactic transients.
30+
31+
Inherits from the HostLessExtragalactic base class.
32+
33+
"""
34+
35+
def process_candidate_fink_rubin(
36+
self, science_stamp: bytes, template_stamp: bytes
37+
) -> Tuple[float, float]:
38+
"""
39+
Processes each candidate
40+
41+
Parameters
42+
----------
43+
science_stamp
44+
science stamp data
45+
template_stamp
46+
template stamp data
47+
"""
48+
science_stamp = read_cutout_stamp(science_stamp)
49+
50+
template_stamp = read_cutout_stamp(template_stamp)
51+
52+
if science_stamp.shape != template_stamp.shape:
53+
return -99, -99
54+
55+
science_stamp_clipped, template_stamp_clipped = self._run_sigma_clipping(
56+
science_stamp, template_stamp
57+
)
58+
is_hostless_candidate = run_hostless_detection_with_clipped_data(
59+
science_stamp_clipped, template_stamp_clipped, self.configs
60+
)
61+
if is_hostless_candidate:
62+
power_spectrum_results = run_powerspectrum_analysis(
63+
science_stamp,
64+
template_stamp,
65+
science_stamp_clipped.mask.astype(int),
66+
template_stamp_clipped.mask.astype(int),
67+
self._image_shape,
68+
)
69+
return power_spectrum_results[
70+
"kstest_SCIENCE_15_statistic"
71+
], power_spectrum_results["kstest_TEMPLATE_15_statistic"]
72+
return -99, -99

fink_science/ztf/hostless_detection/pipeline_utils.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,12 @@ def run_powerspectrum_analysis(
221221
number of iterations for powerspectrum analysis shuffling
222222
223223
"""
224-
science_data = create_noise_filled_mask(science_image, science_mask, image_size)
225-
template_data = create_noise_filled_mask(template_image, template_mask, image_size)
224+
science_data = create_noise_filled_mask(
225+
science_image, science_mask, list(science_mask.shape)
226+
)
227+
template_data = create_noise_filled_mask(
228+
template_image, template_mask, list(template_mask.shape)
229+
)
226230
_, kstest_results_dict, _, _ = ps.detect_host_with_powerspectrum(
227231
science_data,
228232
template_data,

0 commit comments

Comments
 (0)