Skip to content

Commit ece68fb

Browse files
committed
add contrast scaling option and remove optionality of specphot config
1 parent 93bbc6e commit ece68fb

File tree

6 files changed

+102
-35
lines changed

6 files changed

+102
-35
lines changed

src/vampires_dpp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.13.3"
1+
__version__ = "0.13.4"

src/vampires_dpp/cli/new.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def get_base_settings(template: PipelineConfig) -> PipelineConfig:
122122
def get_calib_settings(template: PipelineConfig) -> PipelineConfig:
123123
click.secho("Frame Calibration", bold=True)
124124
readline.set_completer(pathCompleter)
125-
calib_dir = click.prompt("Enter path to calibration files", default="")
125+
calib_dir = click.prompt("Enter path to calibration files (or press enter to skip)", default="")
126126
readline.set_completer()
127127
if calib_dir != "":
128128
template.calibrate.calib_directory = Path(calib_dir)
@@ -305,16 +305,26 @@ def get_diff_image_config(template: PipelineConfig) -> PipelineConfig:
305305
def get_specphot_settings(template: PipelineConfig) -> PipelineConfig:
306306
click.secho("Spectrophotometric Calibration", bold=True)
307307
## Specphot Cal
308-
if click.confirm(
309-
"Would you like to do flux calibration?", default=template.specphot is not None
310-
):
308+
unit_choices = ["e-/s", "contrast", "Jy", "Jy/arcsec^2"]
309+
readline.set_completer(createListCompleter(unit_choices))
310+
unit = click.prompt(
311+
"Choose output units",
312+
type=click.Choice(unit_choices, case_sensitive=False),
313+
default=template.specphot.unit,
314+
)
315+
readline.set_completer()
316+
# default values
317+
source = sptype = mag = mag_band = None
318+
metric = "photometry"
319+
if "Jy" in unit:
311320
readline.set_completer(pathCompleter)
312321
source = click.prompt(
313-
' - Enter source type ("pickles" or path to spectrum)', default="pickles"
322+
' - Enter spectrum source ("pickles" or path to spectrum)', default="pickles"
314323
)
315324
readline.set_completer()
316325
if source == "pickles":
317326
if template.target is not None:
327+
click.echo("...Attempting to look up stellar flux from UCAC4/SIMBAD")
318328
simbad_table = get_simbad_table(template.target.name)
319329
sptype = re.match(r"\w\d[IV]{0,3}", simbad_table["SP_TYPE"][0]).group()
320330
if len(sptype) == 2:
@@ -348,19 +358,15 @@ def get_specphot_settings(template: PipelineConfig) -> PipelineConfig:
348358
default=mag_band,
349359
type=click.Choice(list(FILTERS.keys()), case_sensitive=False),
350360
)
351-
else:
352-
sptype = mag = mag_band = None
353-
361+
if unit != "e-/s":
354362
metric = click.prompt(
355363
" - Select which metric to use for flux",
356364
default="photometry",
357365
type=click.Choice(["photometry", "sum"]),
358366
)
359-
template.specphot = SpecphotConfig(
360-
source=source, sptype=sptype, mag=mag, mag_band=mag_band, flux_metric=metric
361-
)
362-
else:
363-
template.specphot = None
367+
template.specphot = SpecphotConfig(
368+
unit=unit, source=source, sptype=sptype, mag=mag, mag_band=mag_band, flux_metric=metric
369+
)
364370

365371
return template
366372

src/vampires_dpp/constants.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@
1111
SUBARU_LOC: Final[EarthLocation] = EarthLocation(lat=19.825504 * u.deg, lon=-155.4760187 * u.deg)
1212

1313

14-
SATSPOT_REF_NACT = 46.7
15-
# SATSPOT_REF_ANGLE = -83.2
16-
SATSPOT_REF_ANGLE = -88.2
17-
18-
1914
class InstrumentInfo(BaseModel):
2015
@property
2116
def pa_offset(self):

src/vampires_dpp/pipeline/config.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,29 @@ class SpecphotConfig(BaseModel):
128128
Which frame analysis statistic to use for determining flux. "photometry" uses an aperture sum, while "sum" uses the sum in the analysis cutout window.
129129
"""
130130

131-
source: Literal["pickles"] | Path = "pickles"
131+
unit: Literal["e-/s", "contrast", "Jy", "Jy/arcsec^2"] = "e-/s"
132+
source: Literal["pickles"] | Path | None = None
132133
sptype: str | None = None
133134
mag: float | None = None
134-
mag_band: Literal["U", "B", "V", "r", "i", "J", "H", "K"] | None = "V"
135-
unit: Literal["e-/s", "Jy", "Jy/arcsec^2"] = "Jy"
135+
mag_band: Literal["U", "B", "V", "r", "i", "J", "H", "K"] | None = None
136136
flux_metric: Literal["photometry", "sum"] = "photometry"
137137

138+
def model_post_init(self, __context: Any) -> None:
139+
if "Jy" in self.unit:
140+
if self.source is None:
141+
msg = "Must provide a spectrum or specify stellar model if you want to calibrate to Jy"
142+
raise ValueError(msg)
143+
if (
144+
self.source == "pickles"
145+
and self.sptype is None
146+
or self.mag is None
147+
or self.mag_band is None
148+
):
149+
msg = "Must specify target magnitude (and filter) as well as spectral type to use 'pickles' stellar model"
150+
raise ValueError(msg)
151+
152+
return super().model_post_init(__context)
153+
138154

139155
class CalibrateConfig(BaseModel):
140156
"""Config for general image calibration.
@@ -444,7 +460,7 @@ class PipelineConfig(BaseModel):
444460
frame_select: FrameSelectConfig = FrameSelectConfig()
445461
align: AlignmentConfig = AlignmentConfig()
446462
coadd: CoaddConfig = CoaddConfig()
447-
specphot: SpecphotConfig | None = None
463+
specphot: SpecphotConfig = SpecphotConfig()
448464
diff_images: DiffImageConfig = DiffImageConfig()
449465
polarimetry: PolarimetryConfig | None = None
450466

src/vampires_dpp/pipeline/pipeline.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,9 @@ def process_group(self, group, group_key: str, output_path: Path):
272272
)
273273
logger.debug(f"Finished frame alignment for group {group_key}")
274274
## Step 5: Spectrophotometric calibration
275-
if self.config.specphot is not None:
276-
logger.debug(f"Starting specphot cal for group {group_key}")
277-
hdul = specphot_cal_hdul(hdul, metrics=metrics, config=self.config.specphot)
278-
logger.debug(f"Finished specphot cal for group {group_key}")
275+
logger.debug(f"Starting specphot cal for group {group_key}")
276+
hdul = specphot_cal_hdul(hdul, metrics=metrics, config=self.config)
277+
logger.debug(f"Finished specphot cal for group {group_key}")
279278
# Awkward: save registered data AFTER specphot calibration
280279
if self.config.align.save_intermediate and self.config.coadd.coadd:
281280
_, outpath = get_paths(output_path, output_directory=self.paths.aligned)

src/vampires_dpp/specphot/specphot.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,43 @@
1414
SpecphotUnits: TypeAlias = Literal["e-/s", "Jy", "Jy/arcsec^2"]
1515
FluxMetric: TypeAlias = Literal["photometry", "sum"]
1616

17+
1718
# vampires_dpp/data
1819
SCEXAO_AREA: Final = 40.64 * u.m**2
1920
VEGASPEC: Final[SourceSpectrum] = SourceSpectrum.from_vega()
21+
SATSPOT_CONTRAST_POLY: Final = { # Lucas et al. 2024 table 9
22+
10.3: np.polynomial.Polynomial((0, -0.111, 14.217)),
23+
15.5: np.polynomial.Polynomial((0, -0.043, 7.634)),
24+
31.0: np.polynomial.Polynomial((0, -0.002, 0.561)),
25+
}
2026

2127

2228
def specphot_cal_hdul(hdul: fits.HDUList, metrics, config: SpecphotConfig):
2329
# determine any conversion factors
24-
if config.unit in ("Jy", "Jy/arcsec^2"):
30+
if config.specphot.unit in ("Jy", "Jy/arcsec^2"):
2531
assert metrics, "Must provide metrics to calculate photometry"
2632

27-
hdul, inst_flux = measure_inst_flux(hdul, metrics, config.flux_metric)
33+
hdul, inst_flux = measure_inst_flux(
34+
hdul, metrics, config.specphot.flux_metric, satspots=config.coronagraphic
35+
)
2836

29-
match config.unit:
37+
match config.specphot.unit:
3038
case "e-/s":
3139
conv_factor = 1
3240
case "Jy":
33-
conv_factor = determine_jy_factor(hdul, inst_flux, config)
41+
conv_factor = determine_jy_factor(hdul, inst_flux, config.specphot)
3442
case "Jy/arcsec^2":
35-
conv_factor = determine_jy_factor(hdul, inst_flux, config) / hdul[0].header["PXAREA"]
43+
conv_factor = (
44+
determine_jy_factor(hdul, inst_flux, config.specphot) / hdul[0].header["PXAREA"]
45+
)
46+
case "contrast":
47+
conv_factor = determine_contrast_factor(hdul, inst_flux)
3648

3749
hdul[0].data *= conv_factor
3850
hdul["ERR"].data *= conv_factor
3951

4052
info = fits.Header()
41-
info["BUNIT"] = config.unit
53+
info["BUNIT"] = config.specphot.unit
4254

4355
for hdu in hdul:
4456
hdu.header.update(info)
@@ -50,7 +62,7 @@ def _format(number, sigfigs=4):
5062
return float(f"%.{sigfigs-1}g" % number)
5163

5264

53-
def measure_inst_flux(hdul, metrics, flux_metric: FluxMetric):
65+
def measure_inst_flux(hdul, metrics, flux_metric: FluxMetric, satspots: bool = False):
5466
info = fits.Header()
5567

5668
match flux_metric:
@@ -62,13 +74,19 @@ def measure_inst_flux(hdul, metrics, flux_metric: FluxMetric):
6274
# collapse all but wavelength axis
6375
inst_flux = np.nanmedian(np.where(flux <= 0, np.nan, flux), axis=(1, 2))
6476
for flux, hdu in zip(inst_flux, hdul[2:], strict=True):
77+
_, obs_filt = update_header_with_filt_info(hdu.header)
6578
field = hdu.header["FIELD"]
6679
info[f"hierarch DPP SPECPHOT INSTFLUX {field}"] = _format(flux), "[e-/s] Instrumental flux"
6780
info[f"hierarch DPP SPECPHOT INSTMAG {field}"] = (
6881
np.round(-2.5 * np.log10(flux), 3),
6982
"[mag] Instrumental magnitude",
7083
)
71-
84+
# add contrast
85+
contrast = satellite_spot_contrast(hdu.header) if satspots else 1
86+
info[f"hierarch DPP SPECPHOT CONTRAST {field}"] = (
87+
contrast,
88+
"Stellar flux to satspot flux ratio",
89+
)
7290
for hdu in hdul:
7391
hdu.header.update(info)
7492
return hdul, inst_flux
@@ -176,3 +194,36 @@ def get_flux_from_metrics(metrics, config: SpecphotConfig) -> float:
176194
fluxes = metrics["sum"]
177195
weights = 1 / metrics["var"]
178196
return np.nansum(fluxes * weights) / np.nansum(weights)
197+
198+
199+
def satellite_spot_contrast(header: fits.Header) -> float:
200+
# handle required headers
201+
for key in ("X_GRDAMP", "X_GRDSEP", "WAVEAVE"):
202+
if key not in header:
203+
msg = f"Cannot calculate satspot flux ratio\n'{key}' was not found in header."
204+
raise ValueError(msg)
205+
206+
amp = header["X_GRDAMP"] # in um
207+
sep = header["X_GRDSEP"] # in lam/d
208+
wave = header["WAVEAVE"] # in nm
209+
if sep not in SATSPOT_CONTRAST_POLY:
210+
msg = f"No calibration data for astrogrid separation {sep} lam/D"
211+
raise ValueError(msg)
212+
poly = SATSPOT_CONTRAST_POLY[sep]
213+
opd = amp * 1e3 / wave
214+
contrast = poly(opd) # satspot flux to stellar flux ratio
215+
return contrast
216+
217+
218+
def determine_contrast_factor(hdul: fits.HDUList, inst_flux):
219+
header = hdul[0].header
220+
factors = []
221+
for hdu in hdul[2:]:
222+
field = hdu.header["FIELD"]
223+
contrast = header[f"hierarch DPP SPECPHOT CONTRAST {field}"]
224+
spotflux = header[f"hierarch DPP SPECPHOT INSTFLUX {field}"]
225+
226+
starflux = spotflux / contrast
227+
factors.append(1 / starflux)
228+
229+
return np.array(factors)[None, :, None, None]

0 commit comments

Comments
 (0)