Move the processing code under pipeline/
Preparing to merge this repository into a combined astrophotography repo. session-scripts/ becomes pipeline/ because the scripts import layout.py from their own directory and must stay together, and because 'pipeline' says what it is rather than how it came about. observing/ stays at the top level: observing plans are not processing code.
This commit is contained in:
parent
653ca103cd
commit
c6299f41ab
53 changed files with 0 additions and 0 deletions
156
pipeline/gc-validate.py
Normal file
156
pipeline/gc-validate.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
"""
|
||||
gc-validate.py -- step 5. External validation against published catalogues.
|
||||
|
||||
Two independent truth sets fall inside the field:
|
||||
* SIMBAD, object type GlC -- confirmed/catalogued Cen A clusters
|
||||
* SCABS (Taylor et al. 2017, MNRAS 469, 3444) -- deep DECam GC candidates,
|
||||
with V magnitudes, so it can be binned in brightness
|
||||
|
||||
Neither is complete, especially in the inner few arcmin where the galaxy swamps
|
||||
even professional data, so a candidate that matches nothing is UNCONFIRMED, not
|
||||
false. Produces NGC5128-gc-recovery.png and prints the numbers used in the notes.
|
||||
"""
|
||||
import os, numpy as np, warnings
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from astropy.io import fits
|
||||
from astropy.wcs import WCS
|
||||
from astropy.coordinates import SkyCoord
|
||||
|
||||
import layout
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
S = layout.SESSION
|
||||
PIXSCALE = 0.5376
|
||||
NUC_RA, NUC_DEC = 201.365063, -43.019113
|
||||
C_CAND, C_EXT, C_KNOWN, C_ACC = "#2563eb", "#e8710a", "#127a5a", "#8b5cf6"
|
||||
C_INK, C_MUTED, C_GRID = "#1a1a1a", "#5c5c5c", "#d8d8d4"
|
||||
SURF = "#fcfcfb"
|
||||
plt.rcParams.update({
|
||||
"figure.facecolor": SURF, "axes.facecolor": SURF, "axes.edgecolor": C_MUTED,
|
||||
"text.color": C_INK, "xtick.color": C_MUTED, "ytick.color": C_MUTED,
|
||||
"axes.grid": True, "grid.color": C_GRID, "grid.linewidth": 0.6,
|
||||
"axes.axisbelow": True, "font.size": 10, "axes.spines.top": False,
|
||||
"axes.spines.right": False, "legend.frameon": False, "axes.labelcolor": C_INK})
|
||||
|
||||
|
||||
def main():
|
||||
d = dict(np.load(layout.path("_gc_cat.npz"), allow_pickle=True))
|
||||
hdr = fits.getheader(layout.path("master-Luminance.fit"))
|
||||
w = WCS(hdr)
|
||||
cx, cy = [float(v) for v in w.all_world2pix(NUC_RA, NUC_DEC, 0)]
|
||||
cat = SkyCoord(d["ra"], d["dec"], unit="deg")
|
||||
nuc = SkyCoord(NUC_RA, NUC_DEC, unit="deg")
|
||||
|
||||
# ---- reference sets restricted to the frame -----------------------------
|
||||
def infield(ra, dec):
|
||||
x, y = w.all_world2pix(ra, dec, 0)
|
||||
return (x > 30) & (x < 4758) & (y > 30) & (y < 3164), x, y
|
||||
|
||||
z = np.load(layout.path("_simbad.npz"), allow_pickle=True)
|
||||
m = z["otype"].astype(str) == "GlC"
|
||||
sra, sdec = z["ra"][m].astype(float), z["dec"][m].astype(float)
|
||||
inf, _, _ = infield(sra, sdec)
|
||||
sim = SkyCoord(sra[inf], sdec[inf], unit="deg")
|
||||
|
||||
r = np.load(layout.path("_cena_gc_ref.npz"))
|
||||
infr, _, _ = infield(r["ra"], r["dec"])
|
||||
good_p = infr & (r["prob"] >= 0.9) & np.isfinite(r["vmag"])
|
||||
sca = SkyCoord(r["ra"][good_p], r["dec"][good_p], unit="deg")
|
||||
scav = r["vmag"][good_p]
|
||||
print("in-field reference objects: SIMBAD GlC %d ; SCABS p>=0.9 with V %d"
|
||||
% (len(sim), len(sca)))
|
||||
|
||||
cand = d["cand"]
|
||||
ccand = cat[cand]
|
||||
call = cat[d["base"]]
|
||||
|
||||
def recov(ref):
|
||||
i, s, _ = ref.match_to_catalog_sky(ccand)
|
||||
j, s2, _ = ref.match_to_catalog_sky(call)
|
||||
return s.arcsec < 2.0, s2.arcsec < 2.0
|
||||
|
||||
hit_sim, det_sim = recov(sim)
|
||||
hit_sca, det_sca = recov(sca)
|
||||
print("SIMBAD GlC: detected at all %d/%d (%.0f%%), kept as candidate %d/%d (%.0f%%)"
|
||||
% (det_sim.sum(), len(sim), 100 * det_sim.mean(),
|
||||
hit_sim.sum(), len(sim), 100 * hit_sim.mean()))
|
||||
print("SCABS p>=0.9: detected %d/%d (%.0f%%), kept %d/%d (%.0f%%)"
|
||||
% (det_sca.sum(), len(sca), 100 * det_sca.mean(),
|
||||
hit_sca.sum(), len(sca), 100 * hit_sca.mean()))
|
||||
|
||||
# ---- purity -------------------------------------------------------------
|
||||
ci, cs, _ = ccand.match_to_catalog_sky(sim)
|
||||
matched_sim = cs.arcsec < 2.0
|
||||
ci2, cs2, _ = ccand.match_to_catalog_sky(
|
||||
SkyCoord(r["ra"][infr], r["dec"][infr], unit="deg"))
|
||||
matched_sca = cs2.arcsec < 2.0
|
||||
any_match = matched_sim | matched_sca
|
||||
print("\nPURITY: %d candidates; %d (%.0f%%) match SIMBAD GlC or SCABS; "
|
||||
"%d (%.0f%%) unconfirmed"
|
||||
% (cand.sum(), any_match.sum(), 100 * any_match.mean(),
|
||||
(~any_match).sum(), 100 * (~any_match).mean()))
|
||||
st = d["simbad_type"][cand]
|
||||
bad = np.isin(st, ["*", "PM*", "V*", "RR*", "EB*", "LP*"])
|
||||
print(" candidates matching a SIMBAD STAR-type object: %d (%.1f%%)"
|
||||
% (bad.sum(), 100 * bad.mean()))
|
||||
print(" matching a SIMBAD galaxy: %d ; Cepheid: %d ; X-ray/LXB: %d"
|
||||
% ((st == "G").sum(), (st == "Ce*").sum(), np.isin(st, ["X", "LXB"]).sum()))
|
||||
rcand = d["r_arcmin"][cand]
|
||||
print(" unconfirmed fraction inside 8': %.0f%% ; outside 8': %.0f%%"
|
||||
% (100 * (~any_match)[rcand < 8].mean(), 100 * (~any_match)[rcand >= 8].mean()))
|
||||
|
||||
# ---- figure -------------------------------------------------------------
|
||||
fig, ax = plt.subplots(1, 2, figsize=(12.6, 5.0))
|
||||
a = ax[0]
|
||||
vb = np.arange(17.0, 22.6, 0.5)
|
||||
vc = 0.5 * (vb[1:] + vb[:-1])
|
||||
fd, fk, nn = [], [], []
|
||||
for j in range(len(vb) - 1):
|
||||
s = (scav >= vb[j]) & (scav < vb[j + 1])
|
||||
nn.append(s.sum())
|
||||
fd.append(det_sca[s].mean() if s.sum() > 4 else np.nan)
|
||||
fk.append(hit_sca[s].mean() if s.sum() > 4 else np.nan)
|
||||
a.plot(vc, fd, lw=2.2, marker="o", ms=7, color=C_KNOWN,
|
||||
label="detected by our pipeline")
|
||||
a.plot(vc, fk, lw=2.2, marker="s", ms=7, color=C_CAND,
|
||||
label="detected AND kept as a candidate")
|
||||
for x, y, n in zip(vc, fd, nn):
|
||||
if np.isfinite(y) and n > 4:
|
||||
a.annotate("%d" % n, (x, y), xytext=(0, 8), textcoords="offset points",
|
||||
ha="center", fontsize=7.5, color=C_MUTED)
|
||||
a.set_xlabel("SCABS V magnitude"); a.set_ylabel("fraction recovered")
|
||||
a.set_ylim(0, 1.05)
|
||||
a.set_title("a) recovery of published clusters vs brightness", fontsize=10.5, loc="left")
|
||||
a.legend(fontsize=9, labelcolor=C_INK)
|
||||
a.text(0.98, 0.9, "numbers = reference objects per bin", transform=a.transAxes,
|
||||
ha="right", fontsize=8, color=C_MUTED)
|
||||
|
||||
b = ax[1]
|
||||
rsca = sca.separation(nuc).arcmin
|
||||
rb = np.array([0, 2, 4, 6, 9, 12, 16, 21, 27])
|
||||
rc = 0.5 * (rb[1:] + rb[:-1])
|
||||
for arr, c, lab, mk in ((det_sca, C_KNOWN, "detected", "o"),
|
||||
(hit_sca, C_CAND, "kept as candidate", "s")):
|
||||
f, nnr = [], []
|
||||
for j in range(len(rb) - 1):
|
||||
s = (rsca >= rb[j]) & (rsca < rb[j + 1])
|
||||
nnr.append(s.sum())
|
||||
f.append(arr[s].mean() if s.sum() > 4 else np.nan)
|
||||
b.plot(rc, f, lw=2.2, marker=mk, ms=7, color=c, label=lab)
|
||||
b.set_xlabel("projected radius (arcmin)"); b.set_ylabel("fraction recovered")
|
||||
b.set_ylim(0, 1.05)
|
||||
b.set_title("b) the same, vs radius (SCABS p$\\geq$0.9)", fontsize=10.5, loc="left")
|
||||
b.legend(fontsize=9, labelcolor=C_INK)
|
||||
fig.suptitle("External validation against SCABS (Taylor et al. 2017) and SIMBAD",
|
||||
fontsize=12.5, x=0.008, ha="left")
|
||||
fig.tight_layout()
|
||||
fig.savefig(layout.path("NGC5128-gc-recovery.png"), dpi=115, bbox_inches="tight",
|
||||
facecolor=SURF)
|
||||
plt.close(fig)
|
||||
print("wrote NGC5128-gc-recovery.png")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue