Skip to content

Commit efa4f0d

Browse files
authored
Merge pull request #7 from des-science/mdet-cuts
ENH function to make mdet cuts
2 parents c48672c + 516a73a commit efa4f0d

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed

des_y6utils/mdet.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import os
2+
import subprocess
3+
4+
import numpy as np
5+
6+
import healsparse
7+
8+
9+
def make_mdet_cuts(data, version, verbose=False):
10+
"""A function to make the standard metadetection cuts.
11+
12+
Note that this function will download a ~100 MB healsparse mask to
13+
the environment variable `MEDS_DIR`. If no such environment variable
14+
is found, a directory called `MEDS_DIR` will be made in the users
15+
HOME area.
16+
17+
Parameters
18+
----------
19+
data : np.ndarray
20+
The structured array of data to be cut.
21+
version : str or int
22+
The version of the cuts. The versions are whole integers.
23+
verbose : bool, optional
24+
If True, print info as cuts are being made.
25+
Default is False.
26+
27+
Returns
28+
-------
29+
msk : np.ndarray of bool
30+
A boolean array with the cuts. To cut the data, use `data[msk]`.
31+
"""
32+
if str(version) == "1":
33+
return _make_mdet_cuts_v1(data, verbose=verbose)
34+
else:
35+
raise ValueError("the mdet cut version '%r' is not recognized!" % version)
36+
37+
38+
def _make_mdet_cuts_raw_v1(d, verbose=False):
39+
"""The raw v1 cuts come from extensive analysis over summer 2022. They
40+
reflect a first-pass at a consensus set of cuts.
41+
42+
components/comments
43+
44+
- We use wmom for shears and pgauss for fluxes. The weighted moments
45+
appear to have less noise in terms of precision on the mean shear.
46+
The pgauss fluxes remove the effects of the PSF on the fluxes to first
47+
order and match the aperture for colors across bands.
48+
- We use a cut in the pgauss T-Terr plane. This cut removes junk detections
49+
near the wings of stars. For this cut we require pgauss_T_flags == 0 as well.
50+
- We use a signal to noise cut of 10 in wmom.
51+
- We use an error dependent cut in the size ratio. This ensures that as the noise
52+
increases we move the size cut higher to eliminate stellar contamination.
53+
- We use "gold-inspired" cuts for crazy colors.
54+
- We cut especially faint objects in each band.
55+
"""
56+
57+
mag_g = _compute_asinh_mags(d["pgauss_band_flux_g"], 0)
58+
mag_r = _compute_asinh_mags(d["pgauss_band_flux_r"], 1)
59+
mag_i = _compute_asinh_mags(d["pgauss_band_flux_i"], 2)
60+
mag_z = _compute_asinh_mags(d["pgauss_band_flux_z"], 3)
61+
gmr = mag_g - mag_r
62+
rmi = mag_r - mag_i
63+
imz = mag_i - mag_z
64+
65+
msk = np.ones(d.shape[0]).astype(bool)
66+
67+
potential_flag_columns = [
68+
"psfrec_flags",
69+
"wmom_flags",
70+
"pgauss_T_flags",
71+
"pgauss_band_flux_flags_g",
72+
"pgauss_band_flux_flags_r",
73+
"pgauss_band_flux_flags_i",
74+
"pgauss_band_flux_flags_z",
75+
"mask_flags",
76+
]
77+
for col in potential_flag_columns:
78+
if col in d.dtype.names:
79+
msk &= (d[col] == 0)
80+
if verbose:
81+
print("did cut " + col, np.sum(msk))
82+
83+
if "shear_bands" in d.dtype.names:
84+
msk &= (d["shear_bands"] == "123")
85+
if verbose:
86+
print("did cut shear_bands", np.sum(msk))
87+
88+
if "pgauss_s2n" in d.dtype.names:
89+
msk &= (d["pgauss_s2n"] > 5)
90+
if verbose:
91+
print("did cut pgauss_s2n", np.sum(msk))
92+
93+
# now do the rest
94+
msk &= (
95+
(d["wmom_s2n"] > 10)
96+
& (d["mfrac"] < 0.1)
97+
& (np.abs(gmr) < 5)
98+
& (np.abs(rmi) < 5)
99+
& (np.abs(imz) < 5)
100+
& np.isfinite(mag_g)
101+
& np.isfinite(mag_r)
102+
& np.isfinite(mag_i)
103+
& np.isfinite(mag_z)
104+
& (mag_g < 26.5)
105+
& (mag_r < 26.5)
106+
& (mag_i < 26.2)
107+
& (mag_z < 25.6)
108+
& (d["pgauss_T"] < (1.9 - 2.8*d["pgauss_T_err"]))
109+
& (
110+
d["wmom_T_ratio"] >= np.maximum(
111+
1.2,
112+
(1.0 + 3.0*d["wmom_T_err"]/d["wmom_psf_T"])
113+
)
114+
)
115+
)
116+
if verbose:
117+
print("did mdet cuts", np.sum(msk))
118+
119+
return msk
120+
121+
122+
def _make_mdet_cuts_v1(d, verbose=False):
123+
124+
msk = _make_mdet_cuts_raw_v1(d, verbose=verbose)
125+
126+
# apply the mask
127+
mpth = _get_mask_path("y6-combined-hleda-gaiafull-hsmap16384-nomdet.fits")
128+
hmap = healsparse.HealSparseMap.read(mpth)
129+
in_footprint = hmap.get_values_pos(d["ra"], d["dec"], valid_mask=True)
130+
msk &= in_footprint
131+
if verbose:
132+
print("did mask cuts", np.sum(msk))
133+
134+
return msk
135+
136+
137+
def _compute_asinh_mags(flux, i):
138+
"""This function and coefficients are from from Eli. Ask him.
139+
140+
Parameters
141+
----------
142+
flux : float or np.ndarray
143+
The flux.
144+
i : int
145+
The index of the band in griz (i.e., 0 for g, 1 for r, 2 for i, 3 for z).
146+
147+
Returns
148+
-------
149+
mag : float or np.ndarray
150+
The asinh magnitude for the flux.
151+
"""
152+
zp = 30.0
153+
# array is griz
154+
b_array = np.array([3.27e-12, 4.83e-12, 6.0e-12, 9.0e-12])
155+
bscale = np.array(b_array) * 10.**(zp / 2.5)
156+
mag = (
157+
2.5 * np.log10(1.0 / b_array[i])
158+
- np.arcsinh(0.5 * flux / bscale[i]) / (0.4 * np.log(10.0))
159+
)
160+
# mag_err = (
161+
# 2.5 * fluxerr / (
162+
# 2.0 * bscale[i] * np.log(10.0)
163+
# * np.sqrt(1.0 + (0.5 * flux / bscale[i])**2.)
164+
# )
165+
# )
166+
return mag
167+
168+
169+
def _get_mask_path(fname):
170+
# get or make meds dir
171+
meds_dir = os.environ.get("MEDS_DIR", None)
172+
if meds_dir is None:
173+
meds_dir = os.environ.expandvars("${HOME}/MEDS_DIR")
174+
os.makedirs(meds_dir, exist_ok=True)
175+
176+
# download if needed
177+
fpth = os.path.join(meds_dir, fname)
178+
if not os.path.exists(fpth):
179+
_download_fname_from_bnl(fpth)
180+
181+
return fpth
182+
183+
184+
def _download_fname_from_bnl(fpth):
185+
fdir, fname = os.path.split(fpth)
186+
187+
wget_res = subprocess.run("which wget", shell=True, capture_output=True)
188+
curl_res = subprocess.run("which curl", shell=True, capture_output=True)
189+
190+
bnl = "https://www.cosmo.bnl.gov/www/esheldon/data/y6-healsparse"
191+
if wget_res.returncode == 0:
192+
subprocess.run(
193+
"cd %s && wget %s/%s" % (
194+
fdir, bnl, fname,
195+
),
196+
shell=True,
197+
check=True,
198+
capture_output=True,
199+
)
200+
elif curl_res.returncode == 0:
201+
subprocess.run(
202+
"cd %s && curl -L %s/%s --output %s" % (
203+
fdir, bnl, fname, fname,
204+
),
205+
shell=True,
206+
check=True,
207+
capture_output=True,
208+
)
209+
else:
210+
raise RuntimeError(
211+
"Could not download mask '%s' from BNL due "
212+
"to wget or curl missing!" % fname,
213+
)

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ piff
55
scipy
66
jax
77
des-easyaccess
8+
healsparse

0 commit comments

Comments
 (0)