"""Step 4: independent photometry for the whole ALFALFA gold+silver sample via the CDS XMatch service. * 2MASS Extended Source Catalogue (VII/233/xsc): total K-band magnitude and 2MASS axis ratio -> independent check on SDSS/unWISE catalogue photometry (shredding of large galaxies) * AllWISE (II/328/allwise): W1-W4 profile-fit magnitudes -> mid-infrared (warm waste heat) test Outputs: RAW/xm_2mass_xsc.csv, RAW/xm_allwise.csv ; data/alfalfa_phot.csv (merged, one row per AGC) """ import io import os import subprocess import numpy as np import pandas as pd from common import RAW, DATA XMATCH = "https://cdsxmatch.u-strasbg.fr/xmatch/api/v1/sync" def xmatch(df, cat2, radius_arcsec, outfile, force=False): path = os.path.join(RAW, outfile) if os.path.exists(path) and os.path.getsize(path) > 100 and not force: return pd.read_csv(path) up = os.path.join(RAW, "_upload.csv") df[["AGC", "ra", "dec"]].to_csv(up, index=False) # multipart upload is simplest with curl (uses the pre-configured proxy/CA bundle) cmd = ["curl", "-sS", "--max-time", "600", "-X", "POST", "-F", "request=xmatch", "-F", f"distMaxArcsec={radius_arcsec}", "-F", "RESPONSEFORMAT=csv", "-F", f"cat1=@{up}", "-F", "colRA1=ra", "-F", "colDec1=dec", "-F", f"cat2={cat2}", XMATCH] out = subprocess.run(cmd, capture_output=True, text=True, check=True).stdout if not out.startswith("angDist"): raise RuntimeError(f"xmatch failed for {cat2}: {out[:400]}") with open(path, "w") as f: f.write(out) return pd.read_csv(io.StringIO(out)) def main(): s = pd.read_csv(os.path.join(DATA, "alfalfa_resid.csv"), low_memory=False) s["ra"], s["dec"] = s.RAO, s.DEO x = xmatch(s, "vizier:VII/233/xsc", 15, "xm_2mass_xsc.csv") x = x.sort_values("angDist").drop_duplicates("AGC") x = x.rename(columns={"K.ext": "Kext", "e_K.ext": "e_Kext", "Sb/a": "ba_2mass", "angDist": "sep_2mass", "J.ext": "Jext", "H.ext": "Hext"})[ ["AGC", "sep_2mass", "Jext", "Hext", "Kext", "e_Kext", "ba_2mass", "r3sig"]] w = xmatch(s, "vizier:II/328/allwise", 6, "xm_allwise.csv") w = w.sort_values("angDist").drop_duplicates("AGC") keep = ["AGC", "angDist", "AllWISE", "W1mag", "e_W1mag", "W2mag", "e_W2mag", "W3mag", "e_W3mag", "W4mag", "e_W4mag", "ccf", "ex", "qph"] w = w[[c for c in keep if c in w.columns]].rename(columns={"angDist": "sep_wise"}) m = s[["AGC"]].merge(x, on="AGC", how="left").merge(w, on="AGC", how="left") m.to_csv(os.path.join(DATA, "alfalfa_phot.csv"), index=False) print(f" sample rows {len(s)}; 2MASS XSC matches {m.Kext.notna().sum()}; AllWISE matches {m.W1mag.notna().sum()}" f"; W3 with error {m.e_W3mag.notna().sum()}; W4 with error {m.e_W4mag.notna().sum()}") if __name__ == "__main__": main()