Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NRC TA plot enhancements #794

Merged
merged 5 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions webbpsf/mast_wss.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@


@functools.lru_cache
def get_visit_nrc_ta_image(visitid, verbose=True):
def get_visit_nrc_ta_image(visitid, verbose=True, kind='cal'):
"""Retrieve from MAST the NIRCam target acq image for a given visit.

This retrieves an image from MAST and returns it as a HDUList variable
Expand All @@ -599,7 +599,6 @@
from astroquery.mast import Mast
keywords = {
'visit_id': [visitid[1:]], # note: drop the initial character 'V'
'category': ['CAL'],
'exp_type': ['NRC_TACQ']
}

Expand All @@ -616,10 +615,32 @@
t = Mast.service_request(service, params)
filename = t[0]['filename']

# If user manually specifies rate or uncal, retrieve that instead
if kind == 'rate' or kind == 'uncal':
filename = filename.replace('_cal.fits', f'_{kind}.fits')

Check warning on line 620 in webbpsf/mast_wss.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/mast_wss.py#L619-L620

Added lines #L619 - L620 were not covered by tests

if verbose:
print(f"TA filename: {filename}")
mast_file_url = f"https://mast.stsci.edu/api/v0.1/Download/file?uri=mast:JWST/product/{filename}"
ta_hdul = fits.open(mast_file_url)
import urllib

Check warning on line 625 in webbpsf/mast_wss.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/mast_wss.py#L625

Added line #L625 was not covered by tests

try:
ta_hdul = fits.open(mast_file_url)
except urllib.error.HTTPError as err:
if err.code == 401: # Unauthorized

Check warning on line 630 in webbpsf/mast_wss.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/mast_wss.py#L627-L630

Added lines #L627 - L630 were not covered by tests
# Use MAST API to allow retrieval of exclusive access data, if relevant
import astroquery
import tempfile
mast_api_token = os.environ.get('MAST_API_TOKEN', None)
mast_obs = astroquery.mast.ObservationsClass(mast_api_token)
uri = f"mast:JWST/product/{filename}"
with tempfile.NamedTemporaryFile() as temp:
mast_obs.download_file(uri, local_path=temp.name, cache=False)
ta_hdul = astropy.io.fits.open(temp.name)

Check warning on line 639 in webbpsf/mast_wss.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/mast_wss.py#L632-L639

Added lines #L632 - L639 were not covered by tests
else:
raise # re-raise any errors other than 401 for permissions denied

Check warning on line 641 in webbpsf/mast_wss.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/mast_wss.py#L641

Added line #L641 was not covered by tests



return ta_hdul

Expand Down
5 changes: 3 additions & 2 deletions webbpsf/match_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@
inst.pupil_mask = header['CORONMSK'].replace('MASKA', 'MASK')
else:
inst.pupil_mask = header['PUPIL']
inst.image_mask = header['CORONMSK'].replace('MASKA', 'MASK') # note, have to modify the value slightly for
# consistency with the labels used in webbpsf
if 'CORONMSK' in header:
inst.image_mask = header['CORONMSK'].replace('MASKA', 'MASK') # note, have to modify the value slightly for

Check warning on line 61 in webbpsf/match_data.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/match_data.py#L60-L61

Added lines #L60 - L61 were not covered by tests
# consistency with the labels used in webbpsf
# The apername keyword is not always correct for cases with dual-channel coronagraphy
# in some such cases, APERNAME != PPS_APER. Let's ensure we have the proper apername for this channel:
apername = get_nrc_coron_apname(header)
Expand Down
93 changes: 78 additions & 15 deletions webbpsf/trending.py
Original file line number Diff line number Diff line change
Expand Up @@ -1827,8 +1827,8 @@


#### Functions for image comparisons
def show_wfs_ta_img(visitid, ax=None, return_handles=False):
""" Retrieve and display a WFS target acq image"""
def show_nrc_ta_img(visitid, ax=None, return_handles=False):
""" Retrieve and display a NIRCam target acq image"""

hdul = webbpsf.mast_wss.get_visit_nrc_ta_image(visitid)

Expand All @@ -1841,24 +1841,22 @@
cmap = matplotlib.cm.viridis.copy()
cmap.set_bad('orange')


norm = matplotlib.colors.AsinhNorm(linear_width = vmax*0.003, vmax=vmax, #vmin=0)
vmin=-1*rsig)


if ax is None:
ax = plt.gca()
ax.imshow(ta_img - bglevel, norm=norm, cmap=cmap, origin='lower')
ax.set_title(f"WFS TA on {visitid}\n{hdul[0].header['DATE-OBS']}")
ax.set_title(f"NIRCam TA on {visitid}\n{hdul[0].header['DATE-OBS']}")

Check warning on line 1850 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1850

Added line #L1850 was not covered by tests
ax.set_ylabel("[Pixels]")
ax.text(0.05, 0.9, hdul[0].header['TARGPROP'],
color='white', transform=ax.transAxes)


if return_handles:
return hdul, ax, norm, cmap, bglevel

def nrc_ta_image_comparison(visitid):

def nrc_ta_image_comparison(visitid, verbose=False, show_centroid=False):
""" Retrieve a NIRCam target acq image and compare to a simulation

Parameters:
Expand All @@ -1872,30 +1870,95 @@
fig, axes = plt.subplots(figsize=(10,5), ncols=3)

# Get and plot the observed TA image
hdul, ax, norm, cmap, bglevel = show_wfs_ta_img(visitid, ax=axes[0], return_handles=True)
im_obs = hdul['SCI'].data
hdul, ax, norm, cmap, bglevel = show_nrc_ta_img(visitid, ax=axes[0], return_handles=True)
im_obs = hdul['SCI'].data.copy()

Check warning on line 1874 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1873-L1874

Added lines #L1873 - L1874 were not covered by tests
im_obs_err = hdul['ERR'].data
im_obs_dq = hdul['DQ'].data

Check warning on line 1876 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1876

Added line #L1876 was not covered by tests

im_obs_clean = im_obs.copy()
im_obs_clean[im_obs_dq & 1] = np.nan # Mask out any DO_NOT_USE pixels.
im_obs_clean = astropy.convolution.interpolate_replace_nans(im_obs_clean, kernel=np.ones((5, 5)))

Check warning on line 1880 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1878-L1880

Added lines #L1878 - L1880 were not covered by tests

# Make a matching sim
nrc = webbpsf.setup_sim_to_match_file(hdul, verbose=False)
opdname = nrc.pupilopd[0].header['CORR_ID'] + "-NRCA3_FP1-1.fits"
psf = nrc.calc_psf(fov_pixels=64)
if verbose:
print(f"Calculating PSF to match that TA image...")
psf = nrc.calc_psf(fov_pixels=im_obs.shape[0])

Check warning on line 1887 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1885-L1887

Added lines #L1885 - L1887 were not covered by tests

# Align and Shift:

im_sim = psf['DET_DIST'].data # Use the extension including distortion and IPC

im_obs_clean = astropy.convolution.interpolate_replace_nans(im_obs, kernel=np.ones((5,5)))

shift, _, _ = phase_cross_correlation(im_obs_clean, im_sim, upsample_factor=32)
if verbose:
print(f"Shift to register sim to data: {shift} pix")

Check warning on line 1894 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1893-L1894

Added lines #L1893 - L1894 were not covered by tests
im_sim_shifted = scipy.ndimage.shift(im_sim, shift, order=5)

# figure out the background level and scale factor
scalefactor = np.nanmax(im_obs) / im_sim.max()
scalefactor = np.nanmax(im_obs_clean) / im_sim.max()
if verbose:
print(f"Scale factor to match sim to data: {scalefactor}")

Check warning on line 1900 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1898-L1900

Added lines #L1898 - L1900 were not covered by tests

im_sim_scaled_aligned = im_sim_shifted*scalefactor

# Plot
if show_centroid:

Check warning on line 1905 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1905

Added line #L1905 was not covered by tests
### OSS CENTROIDS ###
# First, see if we can retrieve the on-board TA centroid measurment from the OSS engineering DB in MAST
try:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To test it out, I installed misc_jwst but it crashed because I apparently haven't set up my MAST_API_TOKEN environment variable. There's a built-in exception handler in engdb.get_ictm_event_log that terminates the execution. So, until I set the env. variable, I'm now stuck and can't run the code because it will always go with misc_jwst since it's installed. I think in such cases, the code should naturally revert to calculating the centroid using webbpsf methods, instead of halting altogether.

I think it'd be nice to be able to compare the two ways to measure the centroids; could the code calculate and report both centroids and (1) use the OSS if misc_jwst is successful or (2) use the WebbPSF centroid otherwise?

import misc_jwst # Optional dependency, including engineering database access tools

Check warning on line 1909 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1908-L1909

Added lines #L1908 - L1909 were not covered by tests
# If we have that, retrieve the log for this visit, extract the OSS centroids, and convert to same
# coordinate frame as used here:
osslog = misc_jwst.engdb.get_ictm_event_log(hdul[0].header['VSTSTART'], hdul[0].header['VISITEND'])
try:
oss_cen = misc_jwst.engdb.extract_oss_TA_centroids(osslog, 'V' + hdul[0].header['VISIT_ID'])

Check warning on line 1914 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1912-L1914

Added lines #L1912 - L1914 were not covered by tests
# Convert from full-frame (as used by OSS) to detector subarray coords:
oss_cen_sci = nrc._detector_geom_info.aperture.det_to_sci(*oss_cen)
oss_cen_sci_pythonic = np.asarray(oss_cen_sci) - 1 # convert from 1-based pixel indexing to 0-based
oss_centroid_text = f"\n OSS centroid: {oss_cen_sci_pythonic[0]:.2f}, {oss_cen_sci_pythonic[1]:.2f}"
axes[0].scatter(oss_cen_sci_pythonic[0], oss_cen_sci_pythonic[1], color='0.5', marker='+', s=50)
axes[0].text(oss_cen_sci_pythonic[0], oss_cen_sci_pythonic[1], 'OSS ', color='0.9', verticalalignment='center', horizontalalignment='right')
if verbose:
print(f"OSS centroid on board: {oss_cen} (full det coord frame, 1-based)")
print(f"OSS centroid converted: {oss_cen_sci_pythonic} (sci frame in {nrc._detector_geom_info.aperture.AperName}, 0-based)")
full_ap = nrc.siaf[nrc._detector_geom_info.aperture.AperName[0:5] + "_FULL"]
oss_cen_full_sci = np.asarray(full_ap.det_to_sci(*oss_cen)) - 1
print(f"OSS centroid converted: {oss_cen_full_sci} (sci frame in {full_ap.AperName}, 0-based)")

Check warning on line 1926 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1916-L1926

Added lines #L1916 - L1926 were not covered by tests

except RuntimeError:
if verbose:
print("Could not parse TA coordinates from log. TA may have failed?")
oss_cen_sci_pythonic = None

Check warning on line 1931 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1928-L1931

Added lines #L1928 - L1931 were not covered by tests

### WCS COORDINATES ###
import jwst.datamodels
model = jwst.datamodels.open(hdul)
targ_coords = astropy.coordinates.SkyCoord(model.meta.target.ra, model.meta.target.dec, frame='icrs', unit=u.deg)
targ_coords_pix = model.meta.wcs.world_to_pixel(targ_coords) # returns x, y
axes[0].scatter(targ_coords_pix[0], targ_coords_pix[1], color='magenta', marker='+', s=50)
axes[0].text(targ_coords_pix[0], targ_coords_pix[1]+2, 'WCS', color='magenta', verticalalignment='bottom', horizontalalignment='center')
axes[0].text(0.95, 0.18, f'From WCS: {targ_coords_pix[0]:.2f}, {targ_coords_pix[1]:.2f}',

Check warning on line 1940 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1934-L1940

Added lines #L1934 - L1940 were not covered by tests
horizontalalignment='right', verticalalignment='bottom',
transform=axes[0].transAxes,
color='white')

if verbose:
print(f"Star coords from WCS: {targ_coords_pix}")
if oss_cen_sci_pythonic is not None:
print(f"WCS offset = {np.asarray(targ_coords_pix) - oss_cen_sci_pythonic} pix")

Check warning on line 1948 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1945-L1948

Added lines #L1945 - L1948 were not covered by tests

except ImportError:
oss_centroid_text = ""

### WEBBPSF CENTROIDS ###
cen = webbpsf.fwcentroid.fwcentroid(im_obs_clean)
axes[0].scatter(cen[1], cen[0], color='red', marker='+', s=50)
axes[0].text(cen[1], cen[0], ' webbpsf', color='red', verticalalignment='center')
axes[0].text(0.95, 0.05, f' webbpsf Centroid: {cen[1]:.2f}, {cen[0]:.2f}'+oss_centroid_text,

Check warning on line 1957 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1954-L1957

Added lines #L1954 - L1957 were not covered by tests
horizontalalignment='right', verticalalignment='bottom',
transform=axes[0].transAxes,
color='white')

axes[1].imshow(im_sim_scaled_aligned, norm=norm, cmap=cmap, origin='lower')
axes[1].set_title(f"Simulated PSF in F212N\nusing {opdname}")

Expand All @@ -1917,6 +1980,6 @@
plt.tight_layout()


outname = f'wfs_ta_comparison_{visitid}.pdf'
outname = f'nrc_ta_comparison_{visitid}.pdf'

Check warning on line 1983 in webbpsf/trending.py

View check run for this annotation

Codecov / codecov/patch

webbpsf/trending.py#L1983

Added line #L1983 was not covered by tests
plt.savefig(outname)
print(f" => {outname}")
Loading