From e2a33c23fe04cda8704200b3aad52ba17cb2c5aa Mon Sep 17 00:00:00 2001 From: Robert Jedrzejewski Date: Tue, 19 Nov 2024 11:30:36 -0500 Subject: [PATCH 01/26] JP-3664: Fix shuttrid negative values under numpy 2 --- jwst/assign_wcs/nirspec.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jwst/assign_wcs/nirspec.py b/jwst/assign_wcs/nirspec.py index 7902d6c3bc..26149cc262 100644 --- a/jwst/assign_wcs/nirspec.py +++ b/jwst/assign_wcs/nirspec.py @@ -726,8 +726,7 @@ def get_open_msa_slits(prog_id, msa_file, msa_metadata_id, dither_position, quadrant = slitlet_rows[0]['shutter_quadrant'] ycen = j xcen = slitlet_rows[0]['shutter_row'] # grab the first as they are all the same - shutter_id = xcen + (ycen - 1) * 365 # shutter numbers in MSA file are 1-indexed - + shutter_id = np.int64(xcen) + (np.int64(ycen) - 1) * 365 # shutter numbers in MSA file are 1-indexed # Background slits all have source_id=0 in the msa_file, # so assign a unique id based on the slitlet_id source_id = slitlet_id @@ -750,7 +749,7 @@ def get_open_msa_slits(prog_id, msa_file, msa_metadata_id, dither_position, np.nan_to_num(s['estimated_source_in_shutter_x'], nan=0.5), np.nan_to_num(s['estimated_source_in_shutter_y'], nan=0.5)) for s in slitlet_rows if s['background'] == 'N'][0] - shutter_id = xcen + (ycen - 1) * 365 # shutter numbers in MSA file are 1-indexed + shutter_id = np.int64(xcen) + (np.int64(ycen) - 1) * 365 # shutter numbers in MSA file are 1-indexed # y-size jmin = min([s['shutter_column'] for s in slitlet_rows]) From 10b49cc956f2f85eb12ea9c8efbbc3e4d26b0f87 Mon Sep 17 00:00:00 2001 From: Robert Jedrzejewski Date: Mon, 25 Nov 2024 14:51:51 -0500 Subject: [PATCH 02/26] Changelog entry --- changes/8978.assign_wcs.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/8978.assign_wcs.rst diff --git a/changes/8978.assign_wcs.rst b/changes/8978.assign_wcs.rst new file mode 100644 index 0000000000..2771c1d3c6 --- /dev/null +++ b/changes/8978.assign_wcs.rst @@ -0,0 +1 @@ +Fix negative SHUTTRID values under numpy 2 From 99d9af165171fe2704afd9d811691df654ec54ac Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Thu, 15 Aug 2024 12:03:35 -0400 Subject: [PATCH 03/26] Calculate t3 amplitudes (untested) --- jwst/ami/leastsqnrm.py | 37 +++++++++++++++++++++++++++++++++++++ jwst/ami/lg_model.py | 1 + 2 files changed, 38 insertions(+) diff --git a/jwst/ami/leastsqnrm.py b/jwst/ami/leastsqnrm.py index d1c1a89524..75d55e36f3 100644 --- a/jwst/ami/leastsqnrm.py +++ b/jwst/ami/leastsqnrm.py @@ -800,6 +800,43 @@ def populate_symmamparray(amps, n=7): return arr +def t3_amplitudes(amps, n=7) # RC 8/24 + """ + Populate the triple-product amplitude array + (NOT closure amplitudes) + + Parameters + ---------- + amps: 1D float array + fringe visibility between each pair of holes + + n: integer + number of holes + + Returns + ------- + cpamps: 1D float array + triple product amplitude array + """ + + arr = populate_symmamparray(amps, n=n) + + cpamps = np.zeros(int(comb(n, 3))) + + nn = 0 + for kk in range(n - 2): + for ii in range(n - kk - 2): + for jj in range(n - kk - ii - 2): + cpamps[nn + jj] = ( + arr[kk, ii + kk + 1] + * arr[ii + kk + 1, jj + ii + kk + 2] + * arr[jj + ii + kk + 2, kk] + ) + + nn += jj + 1 + + return cpamps + def redundant_cps(deltaps, n=7): """ diff --git a/jwst/ami/lg_model.py b/jwst/ami/lg_model.py index 20ee86f33a..9d7dca72a6 100755 --- a/jwst/ami/lg_model.py +++ b/jwst/ami/lg_model.py @@ -396,6 +396,7 @@ def fit_image( self.fringeamp, self.fringephase = leastsqnrm.tan2visibilities(self.soln) self.fringepistons = utils.fringes2pistons(self.fringephase, len(self.ctrs)) self.redundant_cps = leastsqnrm.redundant_cps(self.fringephase, n=self.N) + self.redundant_cpamps = leastsqnrm.redundant_cpamps(self.fringeamp, n=self.N) # RC 8/24 self.redundant_cas = leastsqnrm.closure_amplitudes(self.fringeamp, n=self.N) def create_modelpsf(self): From edc02aed5bfc4c3aad818fbf32b56c9b6a1d5b2a Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Thu, 15 Aug 2024 12:06:19 -0400 Subject: [PATCH 04/26] noted where t3amps mistakenly populated with closure amps (quad amps) --- jwst/ami/nrm_core.py | 2 ++ jwst/ami/oifits.py | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/jwst/ami/nrm_core.py b/jwst/ami/nrm_core.py index e8e54b68f7..8b8f310096 100755 --- a/jwst/ami/nrm_core.py +++ b/jwst/ami/nrm_core.py @@ -234,3 +234,5 @@ def fit_fringes_single_integration(self, slc): # model now stored as nrm.modelpsf, also nrm.residual self.nrm = nrm # this gets updated with each slice return nrm # to fit_fringes_all, where the output model will be created from list of nrm objects + + diff --git a/jwst/ami/oifits.py b/jwst/ami/oifits.py index e0bad2e233..bd6022f8d7 100644 --- a/jwst/ami/oifits.py +++ b/jwst/ami/oifits.py @@ -79,6 +79,15 @@ def make_obsarrays(self): self.fringe_amplitudes_squared = self.fringe_amplitudes ** 2 # squared visibilities + def average_observables(self): + """ + Convert visamp, visphase arrays to complex visibilities arrays for averaging cv's + Calculate covariance matrices between fringe amplitudes and fringe phases, + and between closure amplutides and closure phases (as well as variance of each). + Convert r, theta (modulus, phase) to x,y. Calculate cov(x,y). Rotate resulting + 2x2 matrix back to r, theta. + """ + def make_oifits(self): """ Perform final manipulations of observable arrays, calculate uncertainties, and @@ -141,6 +150,9 @@ def make_oifits(self): # apply sigma-clipping to uncertainties # sigma_clipped_stats returns mean, median, stddev. nsigma=3, niters=5 + + # HERE: update to use average_observables func + elif self.method == "median": _, self.vis2, self.e_vis2 = sigma_clipped_stats(self.fringe_amplitudes_squared, axis=0) _, self.visamp, self.e_visamp = sigma_clipped_stats(self.fringe_amplitudes, axis=0) @@ -253,8 +265,8 @@ def make_oifits(self): m.t3['TARGET_ID'] = 1 m.t3['TIME'] = 0 m.t3['MJD'] = observation_date.mjd - m.t3['T3AMP'] = self.camp - m.t3['T3AMPERR'] = self.e_camp + m.t3['T3AMP'] = self.camp # WRONG, CHANGE THIS + m.t3['T3AMPERR'] = self.e_camp # WRONG, CHANGE THIS m.t3['T3PHI'] = self.closure_phases m.t3['T3PHIERR'] = self.e_cp m.t3['U1COORD'] = u1coord From bde3d235d61ce394212066d7ea7b4f45e013ce02 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Thu, 15 Aug 2024 12:41:47 -0400 Subject: [PATCH 05/26] Preliminary quad phase calculation --- jwst/ami/leastsqnrm.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/jwst/ami/leastsqnrm.py b/jwst/ami/leastsqnrm.py index 75d55e36f3..5319313341 100644 --- a/jwst/ami/leastsqnrm.py +++ b/jwst/ami/leastsqnrm.py @@ -971,3 +971,38 @@ def closure_amplitudes(amps, n=7): nn = nn + ll + 1 return cas + +def q4_phases(deltaps, n=7): + """ + Calculate phases for each set of 4 holes + + Parameters + ---------- + deltaps: 1D float array + pistons between each pair of holes + + n: integer + number of holes + + Returns + ------- + quad_phases: 1D float array + closure phases + """ + arr = populate_antisymmphasearray(deltaps, n=n) # fringe phase array + + quad_phases = np.zeros(int(comb(n, 4))) + + for ii in range(n - 3): + for jj in range(n - ii - 3): + for kk in range(n - jj - ii - 3): + for ll in range(n - jj - ii - kk - 3): + quad_phases[nn + ll] = ( + arr[ii, jj + ii + 1] + + arr[ll + ii + jj + kk + 3, kk + jj + ii + 2] + - arr[ii, kk + ii + jj + 2] + - arr[jj + ii + 1, ll + ii + jj + kk + 3] + ) + nn = nn + ll + 1 + + return quad_phases From a0e82d7fee932494aaac2d8041f235f3fa2ae86b Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Thu, 15 Aug 2024 12:43:23 -0400 Subject: [PATCH 06/26] docstring correction --- jwst/ami/leastsqnrm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jwst/ami/leastsqnrm.py b/jwst/ami/leastsqnrm.py index 5319313341..c6e4434bd0 100644 --- a/jwst/ami/leastsqnrm.py +++ b/jwst/ami/leastsqnrm.py @@ -987,7 +987,7 @@ def q4_phases(deltaps, n=7): Returns ------- quad_phases: 1D float array - closure phases + quad phases """ arr = populate_antisymmphasearray(deltaps, n=n) # fringe phase array From f869bd724368ed2bb51b74eb7ba36c096419f682 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Wed, 25 Sep 2024 16:10:47 -0400 Subject: [PATCH 07/26] Updates to use covariances for error estimates and calculate observables from already-averaged fringe phases or amplitudes, WIP --- jwst/ami/lg_model.py | 3 +- jwst/ami/oifits.py | 195 +++++++++++++++++++++++++++++++++---------- 2 files changed, 154 insertions(+), 44 deletions(-) diff --git a/jwst/ami/lg_model.py b/jwst/ami/lg_model.py index 9d7dca72a6..2981baaa8a 100755 --- a/jwst/ami/lg_model.py +++ b/jwst/ami/lg_model.py @@ -396,8 +396,9 @@ def fit_image( self.fringeamp, self.fringephase = leastsqnrm.tan2visibilities(self.soln) self.fringepistons = utils.fringes2pistons(self.fringephase, len(self.ctrs)) self.redundant_cps = leastsqnrm.redundant_cps(self.fringephase, n=self.N) - self.redundant_cpamps = leastsqnrm.redundant_cpamps(self.fringeamp, n=self.N) # RC 8/24 + self.t3_amplitudes = leastsqnrm.t3_amplitudes(self.fringeamp, n=self.N) # RC 8/24 self.redundant_cas = leastsqnrm.closure_amplitudes(self.fringeamp, n=self.N) + self.q4_phases = leastsqnrm.q4_phases(self.fringephase, n=self.N) # RC 8/24 def create_modelpsf(self): """ diff --git a/jwst/ami/oifits.py b/jwst/ami/oifits.py index bd6022f8d7..b2d9d1acb9 100644 --- a/jwst/ami/oifits.py +++ b/jwst/ami/oifits.py @@ -41,7 +41,7 @@ def __init__(self, fringefitter, method="median"): self.nslices = len(self.fringe_fitter.nrm_list) # n ints self.n_baselines = int(comb(self.n_holes, 2)) # 21 self.n_closure_phases = int(comb(self.n_holes, 3)) # 35 - self.n_closure_amplitudes = int(comb(self.n_holes, 4)) + self.n_closure_amplitudes = int(comb(self.n_holes, 4)) # also 35 self.method = method @@ -53,40 +53,141 @@ def __init__(self, fringefitter, method="median"): self.bholes, self.bls = self._makebaselines() self.tholes, self.tuv = self._maketriples_all() + self.qholes, self.quads = self._makequads_all() def make_obsarrays(self): """ Make arrays of observables of the correct shape for saving to datamodels """ - # arrays of observables, (nslices,nobservables) shape. + # empty arrays of observables, (nslices,nobservables) shape. self.fringe_phases = np.zeros((self.nslices, self.n_baselines)) self.fringe_amplitudes = np.zeros((self.nslices, self.n_baselines)) self.closure_phases = np.zeros((self.nslices, self.n_closure_phases)) + self.t3_amplitudes = np.zeros((self.nslices, self.n_closure_phases)) + self.q4_phases = np.zeros((self.nslices, self.n_closure_amplitudes)) self.closure_amplitudes = np.zeros((self.nslices, self.n_closure_amplitudes)) self.pistons = np.zeros((self.nslices, self.n_holes)) # model parameters self.solns = np.zeros((self.nslices, 44)) + # populate with each integration's observables for i, nrmslc in enumerate(self.fringe_fitter.nrm_list): - self.fringe_phases[i, :] = np.rad2deg(nrmslc.fringephase) # FPs in degrees + self.fringe_phases[i, :] = nrmslc.fringephase # FPs in radians self.fringe_amplitudes[i, :] = nrmslc.fringeamp - self.closure_phases[i, :] = np.rad2deg(nrmslc.redundant_cps) # CPs in degrees + self.closure_phases[i, :] = nrmslc.redundant_cps # CPs in radians + self.t3_amplitudes[i, :] = nrmslc.t3_amplitudes + self.q4_phases[i, :] = nrmslc.q4_phases # quad phases in radians self.closure_amplitudes[i, :] = nrmslc.redundant_cas - self.pistons[i, :] = np.rad2deg( - nrmslc.fringepistons - ) # segment pistons in degrees + self.pistons[i, :] = nrmslc.fringepistons # segment pistons in radians self.solns[i, :] = nrmslc.soln self.fringe_amplitudes_squared = self.fringe_amplitudes ** 2 # squared visibilities + def rotate_matrix(self, cov_mat, theta): + """ + Rotate a covariance matrix by an angle. + """ + c, s = np.cos(theta), np.sin(theta) + R_mat = [[c, -s], + [s, c]] + # coordinate rotation from real/imaginary to absolute value/phase (modulus/argument) + cv_rotated = np.linalg.multi_dot([np.transpose(R_mat), cov_mat, R_mat]) + return cv_rotated + + def average_observables(self): """ - Convert visamp, visphase arrays to complex visibilities arrays for averaging cv's - Calculate covariance matrices between fringe amplitudes and fringe phases, - and between closure amplutides and closure phases (as well as variance of each). + Calculate covariance matrices between fringe amplitudes/fringe phases, + and between triple product amps/closure phases, and closure amplitudes/quad phases. Convert r, theta (modulus, phase) to x,y. Calculate cov(x,y). Rotate resulting - 2x2 matrix back to r, theta. + 2x2 matrix back to r, theta. Take sqrt of relevant covariance element to be error. + This must be done with phases in radians + self.method gives averaging method + Input names in nrm differ from implaneia! + + """ + covmats_fringes, covmats_triples, covmats_quads = observable_covariances(nrm, averfunc) + + if self.method == 'median': + _, avg_fa, std_fa = sigma_clipped_stats(self.fringe_amplitudes, axis=0) # 21. std_fa is just for comparing to covariance + _, avg_fp, std_fp = sigma_clipped_stats(self.fringe_phases, axis=0) # 21 + _, avg_sqv, err_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) + _, avg_pist, err_pist = sigma_clipped_stats(self.pistons, axis=0) + else: # mean + avg_fa, _, std_fa = sigma_clipped_stats(self.fringe_amplitudes, axis=0) + avg_fp, _, std_fp = sigma_clipped_stats(self.fringe_phases, axis=0) + avg_sqv, _, err_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) + avg_pist, _, err_pist = sigma_clipped_stats(self.pistons, axis=0) + + err_fa, err_fp = err_from_covmat(covmats_fringes) + + # calculate triple and quad quantities from **averaged** fringe amps and phases + avg_t3amp = leastsqnrm.t3_amplitudes(avg_fa, n=self.n_holes) + avg_cp = leastsqnrm.redundant_cps(avg_fp, n=self.n_holes) + err_t3amp, err_cp = err_from_covmat(covmats_triples) + + avg_ca = leastsqnrm.closure_amplitudes(avg_fa, n=self.n_holes) + avg_q4phi = leastsqnrm.q4_phases(avg_fp, n=self.n_holes) + err_ca, err_q4phi = err_from_covmat(covmats_quads) + + return avg_sqv, err_sqv, avg_fa, err_fa, avg_fp, err_fp, avg_cp, err_cp, avg_t3amp, err_t3amp, avg_ca, err_ca, avg_q4phi, err_q4phi, avg_pist, err_pist + + def err_from_covmat(self, covmatlist): + """ + Return sqrt of [0,0] and [1,1] elements of each of a list of covariance matrices, + for use as observable errors. + """ + err_00 = np.sqrt(np.array([covmat[0,0] for covmat in covmatlist])) + err_11 = np.sqrt(np.array([covmat[1,1] for covmat in covmatlist])) + + return err_00, err_11 + + def observable_covariances(self, averfunc): + """ + input: nrm: ObservablesFromText instance + """ + # loop over 21 baselines + cov_mat_fringes = [] + # these currently operate on all slices at once. other one operated on already-averaged slices + for bl in np.arange(self.n_baselines): + fringeamps = self.fringe_amplitudes[:,bl] + fringephases = self.fringe_phases[:,bl] + covmat = cov_r_theta(fringeamps, fringephases, averfunc) + cov_mat_fringes.append(covmat) + # loop over 35 triples + cov_mat_triples = [] + for triple in np.arange(self.n_closure_phases): + tripamp = self.t3_amplitudes[:,triple] + triphase = self.closure_phases[:,triple] + covmat = cov_r_theta(tripamp, triphase, averfunc) + cov_mat_triples.append(covmat) + # loop over 35 quads + cov_mat_quads = [] + for quad in np.arange(self.n_closure_amplitudes): + quadamp = self.closure_amplitudes[:,quad] + quadphase = self.q4_phases[:,quad] + covmat = cov_r_theta(quadamp, quadphase, averfunc) + cov_mat_quads.append(covmat) + + # covmats to be written to oifits. store in nrm object? + # store entire matrix... somewhere + # lists of cov mats have shape e.g. (21, 2, 2) or (35, 2, 2) + return np.array(cov_mat_fringes), np.array(cov_mat_triples), np.array(cov_mat_quads) + + + def cov_r_theta(self, rr, theta, averfunc): + """ + rr: complex number modulus, array + theta: complex number phase, array + averfunc: np.median or np.mean + rotating by **average** phase (over integrations) """ + xx = rr * np.cos(theta) + yy = rr * np.sin(theta) + cov_mat_xy = np.cov(xx, yy) + cov_mat_r_theta = rotate_matrix(cov_mat_xy, averfunc(theta)) + return cov_mat_r_theta + def make_oifits(self): """ @@ -130,7 +231,9 @@ def make_oifits(self): shift2pi[self.closure_phases >= 6] = 2 * np.pi shift2pi[self.closure_phases <= -6] = -2 * np.pi self.closure_phases -= shift2pi + # this is problematic because phases were converted to DEGREES in make_obsarrays()? + # Now we are setting up the observables to be written out to OIFITS if self.method not in ["mean", "median", "multi"]: self.method = "median" # set these as attributes (some may exist and be overwritten) @@ -143,31 +246,21 @@ def make_oifits(self): self.e_visphi = np.zeros(self.visphi.shape) self.closure_phases = self.closure_phases.T self.e_cp = np.zeros(self.closure_phases.shape) + self.t3amp = self.t3_amplitudes.T + self.e_t3amp = np.zeros(self.t3amp.shape) + self.q4phi = self.q4_phases.T + self.e_q4phi = np.zeros(self.q4_phases.shape) self.camp = self.closure_amplitudes.T self.e_camp = np.zeros(self.camp.shape) self.pist = self.pistons.T self.e_pist = np.zeros(self.pist.shape) - # apply sigma-clipping to uncertainties - # sigma_clipped_stats returns mean, median, stddev. nsigma=3, niters=5 - - # HERE: update to use average_observables func elif self.method == "median": - _, self.vis2, self.e_vis2 = sigma_clipped_stats(self.fringe_amplitudes_squared, axis=0) - _, self.visamp, self.e_visamp = sigma_clipped_stats(self.fringe_amplitudes, axis=0) - _, self.visphi, self.e_visphi = sigma_clipped_stats(self.fringe_phases, axis=0) - _, self.closure_phases, self.e_cp = sigma_clipped_stats(self.closure_phases, axis=0) - _, self.camp, self.e_camp = sigma_clipped_stats(self.closure_amplitudes, axis=0) - _, self.pist, self.e_pist = sigma_clipped_stats(self.pistons, axis=0) + self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.cp, self.e_cp, self.t3amp, self.e_t3amp, self.ca, self.e_ca, self.q4phi, self.e_q4phi, self.pist, self.e_pist = average_observables(self, np.median) else: # take the mean - self.vis2, _, self.e_vis2 = sigma_clipped_stats(self.fringe_amplitudes_squared, axis=0) - self.visamp, _, self.e_visamp = sigma_clipped_stats(self.fringe_amplitudes, axis=0) - self.visphi, _, self.e_visphi = sigma_clipped_stats(self.fringe_phases, axis=0) - self.closure_phases, _, self.e_cp = sigma_clipped_stats(self.closure_phases, axis=0) - self.camp, _, self.e_camp = sigma_clipped_stats(self.closure_amplitudes, axis=0) - self.pist, _, self.e_pist = sigma_clipped_stats(self.pistons, axis=0) + self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.cp, self.e_cp, self.t3amp, self.e_t3amp, self.ca, self.e_ca, self.q4phi, self.e_q4phi, self.pist, self.e_pist = average_observables(self, np.mean) # prepare arrays for OI_ARRAY ext self.staxy = instrument_data.ctrs_inst @@ -265,8 +358,8 @@ def make_oifits(self): m.t3['TARGET_ID'] = 1 m.t3['TIME'] = 0 m.t3['MJD'] = observation_date.mjd - m.t3['T3AMP'] = self.camp # WRONG, CHANGE THIS - m.t3['T3AMPERR'] = self.e_camp # WRONG, CHANGE THIS + m.t3['T3AMP'] = self.t3amp + m.t3['T3AMPERR'] = self.e_t3amp m.t3['T3PHI'] = self.closure_phases m.t3['T3PHIERR'] = self.e_cp m.t3['U1COORD'] = u1coord @@ -396,26 +489,19 @@ def _maketriples_all(self): float array of two uv vectors in all triangles """ tlist = [] + uvlist = [] for i in range(self.n_holes): for j in range(self.n_holes): for k in range(self.n_holes): if i < j and j < k: tlist.append((i, j, k)) + uvlist.append( + ( + self.ctrs_eqt[i] - self.ctrs_eqt[j], + self.ctrs_eqt[j] - self.ctrs_eqt[k], + ) + ) tarray = np.array(tlist).astype(int) - - tname = [] - uvlist = [] - # foreach row of 3 elts... - for triple in tarray: - tname.append("{0:d}_{1:d}_{2:d}".format(triple[0], triple[1], triple[2])) - - uvlist.append( - ( - self.ctrs_eqt[triple[0]] - self.ctrs_eqt[triple[1]], - self.ctrs_eqt[triple[1]] - self.ctrs_eqt[triple[2]], - ) - ) - return tarray, np.array(uvlist) def _makebaselines(self): @@ -437,6 +523,29 @@ def _makebaselines(self): bllist.append(self.ctrs_eqt[i] - self.ctrs_eqt[j]) return np.array(blist).astype(int), np.array(bllist) + def _makequads_all(self): + """ + Calculate all four-hole combinations (quads) + + Returns + ------- + qarray: int array of four-hole quads (0-based) + uvwlist: numpy array of u, v, w vectors for each quad + """ + qlist = [] + uvwlist = [] + for i in range(self.n_holes): + for j in range(self.n_holes): + for k in range(self.n_holes): + for q in range(self.n_holes): + if i < j and j < k and k < q: + qlist.append((i, j, k, q)) + uvwlist.append((self.ctrs_eqt[i] - self.ctrs_eqt[j], + self.ctrs_eqt[j] - self.ctrs_eqt[k], + self.ctrs_eqt[k] - self.ctrs_eqt[q])) + qarray = np.array(qlist).astype(int) + return qarray, np.array(uvwlist) + def _format_STAINDEX_T3(self, tab): """ Converts sta_index to save oifits T3 in the appropriate format From 182f1d47f3f8f391c1a4c821fcc6b40390d5f02b Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Thu, 26 Sep 2024 13:22:38 -0400 Subject: [PATCH 08/26] Debugging fixes, now functioning --- jwst/ami/leastsqnrm.py | 4 ++-- jwst/ami/oifits.py | 23 ++++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/jwst/ami/leastsqnrm.py b/jwst/ami/leastsqnrm.py index c6e4434bd0..f7894278e3 100644 --- a/jwst/ami/leastsqnrm.py +++ b/jwst/ami/leastsqnrm.py @@ -800,7 +800,7 @@ def populate_symmamparray(amps, n=7): return arr -def t3_amplitudes(amps, n=7) # RC 8/24 +def t3_amplitudes(amps, n=7): """ Populate the triple-product amplitude array (NOT closure amplitudes) @@ -990,7 +990,7 @@ def q4_phases(deltaps, n=7): quad phases """ arr = populate_antisymmphasearray(deltaps, n=n) # fringe phase array - + nn = 0 quad_phases = np.zeros(int(comb(n, 4))) for ii in range(n - 3): diff --git a/jwst/ami/oifits.py b/jwst/ami/oifits.py index b2d9d1acb9..ef20b6fde8 100644 --- a/jwst/ami/oifits.py +++ b/jwst/ami/oifits.py @@ -14,6 +14,7 @@ from astropy.time.core import Time from stdatamodels.jwst import datamodels +from . import leastsqnrm class RawOifits: @@ -95,7 +96,7 @@ def rotate_matrix(self, cov_mat, theta): return cv_rotated - def average_observables(self): + def average_observables(self, averfunc): """ Calculate covariance matrices between fringe amplitudes/fringe phases, and between triple product amps/closure phases, and closure amplitudes/quad phases. @@ -106,7 +107,7 @@ def average_observables(self): Input names in nrm differ from implaneia! """ - covmats_fringes, covmats_triples, covmats_quads = observable_covariances(nrm, averfunc) + covmats_fringes, covmats_triples, covmats_quads = self.observable_covariances(averfunc) if self.method == 'median': _, avg_fa, std_fa = sigma_clipped_stats(self.fringe_amplitudes, axis=0) # 21. std_fa is just for comparing to covariance @@ -119,16 +120,16 @@ def average_observables(self): avg_sqv, _, err_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) avg_pist, _, err_pist = sigma_clipped_stats(self.pistons, axis=0) - err_fa, err_fp = err_from_covmat(covmats_fringes) + err_fa, err_fp = self.err_from_covmat(covmats_fringes) # calculate triple and quad quantities from **averaged** fringe amps and phases avg_t3amp = leastsqnrm.t3_amplitudes(avg_fa, n=self.n_holes) avg_cp = leastsqnrm.redundant_cps(avg_fp, n=self.n_holes) - err_t3amp, err_cp = err_from_covmat(covmats_triples) + err_t3amp, err_cp = self.err_from_covmat(covmats_triples) avg_ca = leastsqnrm.closure_amplitudes(avg_fa, n=self.n_holes) avg_q4phi = leastsqnrm.q4_phases(avg_fp, n=self.n_holes) - err_ca, err_q4phi = err_from_covmat(covmats_quads) + err_ca, err_q4phi = self.err_from_covmat(covmats_quads) return avg_sqv, err_sqv, avg_fa, err_fa, avg_fp, err_fp, avg_cp, err_cp, avg_t3amp, err_t3amp, avg_ca, err_ca, avg_q4phi, err_q4phi, avg_pist, err_pist @@ -152,21 +153,21 @@ def observable_covariances(self, averfunc): for bl in np.arange(self.n_baselines): fringeamps = self.fringe_amplitudes[:,bl] fringephases = self.fringe_phases[:,bl] - covmat = cov_r_theta(fringeamps, fringephases, averfunc) + covmat = self.cov_r_theta(fringeamps, fringephases, averfunc) cov_mat_fringes.append(covmat) # loop over 35 triples cov_mat_triples = [] for triple in np.arange(self.n_closure_phases): tripamp = self.t3_amplitudes[:,triple] triphase = self.closure_phases[:,triple] - covmat = cov_r_theta(tripamp, triphase, averfunc) + covmat = self.cov_r_theta(tripamp, triphase, averfunc) cov_mat_triples.append(covmat) # loop over 35 quads cov_mat_quads = [] for quad in np.arange(self.n_closure_amplitudes): quadamp = self.closure_amplitudes[:,quad] quadphase = self.q4_phases[:,quad] - covmat = cov_r_theta(quadamp, quadphase, averfunc) + covmat = self.cov_r_theta(quadamp, quadphase, averfunc) cov_mat_quads.append(covmat) # covmats to be written to oifits. store in nrm object? @@ -185,7 +186,7 @@ def cov_r_theta(self, rr, theta, averfunc): xx = rr * np.cos(theta) yy = rr * np.sin(theta) cov_mat_xy = np.cov(xx, yy) - cov_mat_r_theta = rotate_matrix(cov_mat_xy, averfunc(theta)) + cov_mat_r_theta = self.rotate_matrix(cov_mat_xy, averfunc(theta)) return cov_mat_r_theta @@ -257,10 +258,10 @@ def make_oifits(self): elif self.method == "median": - self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.cp, self.e_cp, self.t3amp, self.e_t3amp, self.ca, self.e_ca, self.q4phi, self.e_q4phi, self.pist, self.e_pist = average_observables(self, np.median) + self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.closure_phases, self.e_cp, self.t3amp, self.e_t3amp, self.camp, self.e_camp, self.q4phi, self.e_q4phi, self.pist, self.e_pist = self.average_observables(np.median) else: # take the mean - self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.cp, self.e_cp, self.t3amp, self.e_t3amp, self.ca, self.e_ca, self.q4phi, self.e_q4phi, self.pist, self.e_pist = average_observables(self, np.mean) + self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.closure_phases, self.e_cp, self.t3amp, self.e_t3amp, self.camp, self.e_camp, self.q4phi, self.e_q4phi, self.pist, self.e_pist = self.average_observables(np.mean) # prepare arrays for OI_ARRAY ext self.staxy = instrument_data.ctrs_inst From 55cef8a6a3cea707023b02118b7f70b9cd08c6fc Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Thu, 26 Sep 2024 15:33:21 -0400 Subject: [PATCH 09/26] convert quantities to degrees and fix squared visibility uncertainties --- jwst/ami/oifits.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/jwst/ami/oifits.py b/jwst/ami/oifits.py index ef20b6fde8..b287378f1a 100644 --- a/jwst/ami/oifits.py +++ b/jwst/ami/oifits.py @@ -112,16 +112,19 @@ def average_observables(self, averfunc): if self.method == 'median': _, avg_fa, std_fa = sigma_clipped_stats(self.fringe_amplitudes, axis=0) # 21. std_fa is just for comparing to covariance _, avg_fp, std_fp = sigma_clipped_stats(self.fringe_phases, axis=0) # 21 - _, avg_sqv, err_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) + _, avg_sqv, std_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) _, avg_pist, err_pist = sigma_clipped_stats(self.pistons, axis=0) else: # mean avg_fa, _, std_fa = sigma_clipped_stats(self.fringe_amplitudes, axis=0) avg_fp, _, std_fp = sigma_clipped_stats(self.fringe_phases, axis=0) - avg_sqv, _, err_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) + avg_sqv, _, std_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) avg_pist, _, err_pist = sigma_clipped_stats(self.pistons, axis=0) err_fa, err_fp = self.err_from_covmat(covmats_fringes) + # calculate squared visibility (fringe) amplitude uncertainties correctly + err_sqv = 2 * avg_fa * err_fa + # calculate triple and quad quantities from **averaged** fringe amps and phases avg_t3amp = leastsqnrm.t3_amplitudes(avg_fa, n=self.n_holes) avg_cp = leastsqnrm.redundant_cps(avg_fp, n=self.n_holes) @@ -262,13 +265,23 @@ def make_oifits(self): else: # take the mean self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.closure_phases, self.e_cp, self.t3amp, self.e_t3amp, self.camp, self.e_camp, self.q4phi, self.e_q4phi, self.pist, self.e_pist = self.average_observables(np.mean) + + # Convert angular quantities from radians to degrees + self.visphi = np.rad2deg(self.visphi) + self.e_visphi = np.rad2deg(self.e_visphi) + self.closure_phases = np.rad2deg(self.closure_phases) + self.e_cp = np.rad2deg(self.e_cp) + self.q4phi = np.rad2deg(self.q4phi) + self.e_q4phi = np.rad2deg(self.e_q4phi) + self.pist = np.rad2deg(self.pist) + self.e_pist = np.rad2deg(self.e_pist) + # prepare arrays for OI_ARRAY ext self.staxy = instrument_data.ctrs_inst - N_ap = len(self.staxy) - tel_name = ["A%i" % x for x in np.arange(N_ap) + 1] + tel_name = ["A%i" % x for x in np.arange(self.n_holes) + 1] sta_name = tel_name - diameter = [0] * N_ap + diameter = [0] * self.n_holes staxyz = [] for x in self.staxy: @@ -276,14 +289,14 @@ def make_oifits(self): line = [a[0], a[1], 0] staxyz.append(line) - sta_index = np.arange(N_ap) + 1 + sta_index = np.arange(self.n_holes) + 1 pscale = instrument_data.pscale_mas / 1000.0 # arcsec isz = self.fringe_fitter.scidata.shape[ 1 ] # Size of the image to extract NRM data - fov = [pscale * isz] * N_ap - fovtype = ["RADIUS"] * N_ap + fov = [pscale * isz] * self.n_holes + fovtype = ["RADIUS"] * self.n_holes m = datamodels.AmiOIModel() self.init_oimodel_arrays(m) From 3417d8eb62132a1eafcfd76935df024d68c69580 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Tue, 1 Oct 2024 11:21:38 -0400 Subject: [PATCH 10/26] Updated to use mean by default and calculate standard error of the mean for uncertainty estimates --- jwst/ami/oifits.py | 106 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 26 deletions(-) diff --git a/jwst/ami/oifits.py b/jwst/ami/oifits.py index b287378f1a..ed9f342429 100644 --- a/jwst/ami/oifits.py +++ b/jwst/ami/oifits.py @@ -18,7 +18,7 @@ class RawOifits: - def __init__(self, fringefitter, method="median"): + def __init__(self, fringefitter, method="mean"): """ Class to store AMI data in the format required to write out to OIFITS files Angular quantities of input are in radians from fringe fitting; converted to degrees for saving. @@ -29,7 +29,7 @@ def __init__(self, fringefitter, method="median"): Object containing nrm_list attribute (list of nrm objects) and other info needed for OIFITS files method: string - Method to average observables: mean or median. Default median. + Method to average observables: mean or median. Default mean. Notes ----- @@ -87,6 +87,16 @@ def make_obsarrays(self): def rotate_matrix(self, cov_mat, theta): """ Rotate a covariance matrix by an angle. + + Parameters + ---------- + cov_mat: + theta: float + Angle by which to rotate the matrix (radians) + + Returns + ------- + """ c, s = np.cos(theta), np.sin(theta) R_mat = [[c, -s], @@ -102,28 +112,35 @@ def average_observables(self, averfunc): and between triple product amps/closure phases, and closure amplitudes/quad phases. Convert r, theta (modulus, phase) to x,y. Calculate cov(x,y). Rotate resulting 2x2 matrix back to r, theta. Take sqrt of relevant covariance element to be error. - This must be done with phases in radians - self.method gives averaging method - Input names in nrm differ from implaneia! + This must be done with phases in radians. + + Parameters + ---------- + averfunc + + Returns + ------- + avg_sqv, err_sqv, avg_fa, err_fa, avg_fp, err_fp, avg_cp, err_cp, avg_t3amp, err_t3amp, avg_ca, err_ca, avg_q4phi, err_q4phi, avg_pist, err_pist """ covmats_fringes, covmats_triples, covmats_quads = self.observable_covariances(averfunc) - if self.method == 'median': - _, avg_fa, std_fa = sigma_clipped_stats(self.fringe_amplitudes, axis=0) # 21. std_fa is just for comparing to covariance - _, avg_fp, std_fp = sigma_clipped_stats(self.fringe_phases, axis=0) # 21 - _, avg_sqv, std_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) - _, avg_pist, err_pist = sigma_clipped_stats(self.pistons, axis=0) - else: # mean + if self.method == 'mean': avg_fa, _, std_fa = sigma_clipped_stats(self.fringe_amplitudes, axis=0) avg_fp, _, std_fp = sigma_clipped_stats(self.fringe_phases, axis=0) avg_sqv, _, std_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) avg_pist, _, err_pist = sigma_clipped_stats(self.pistons, axis=0) + else: # median + _, avg_fa, std_fa = sigma_clipped_stats(self.fringe_amplitudes, axis=0) # 21. std_fa is just for comparing to covariance + _, avg_fp, std_fp = sigma_clipped_stats(self.fringe_phases, axis=0) # 21 + _, avg_sqv, std_sqv = sigma_clipped_stats(self.fringe_amplitudes**2, axis=0) + _, avg_pist, err_pist = sigma_clipped_stats(self.pistons, axis=0) + err_pist = err_pist/np.sqrt(self.nslices) # standard error of the mean err_fa, err_fp = self.err_from_covmat(covmats_fringes) # calculate squared visibility (fringe) amplitude uncertainties correctly - err_sqv = 2 * avg_fa * err_fa + err_sqv = (2 * avg_fa * err_fa) / np.sqrt(self.nslices) # calculate triple and quad quantities from **averaged** fringe amps and phases avg_t3amp = leastsqnrm.t3_amplitudes(avg_fa, n=self.n_holes) @@ -139,16 +156,41 @@ def average_observables(self, averfunc): def err_from_covmat(self, covmatlist): """ Return sqrt of [0,0] and [1,1] elements of each of a list of covariance matrices, - for use as observable errors. + divided by sqrt(N_ints), for use as observable errors. (standard error of the mean) + If using median, error calculation is questionable because this is NOT the standard + error of the median + + Parameters + ---------- + covmatlist: array + array of covariance matrices for each baseline/triple/quad + shape e.g. (21,2,2) or (35,2,2) + + Returns + ------- + err_00: array + standard errors of the mean of the first observable. shape e.g. (21) + err_11: array + standard errors of the mean of the second observable. shape e.g. (21) """ - err_00 = np.sqrt(np.array([covmat[0,0] for covmat in covmatlist])) - err_11 = np.sqrt(np.array([covmat[1,1] for covmat in covmatlist])) + err_00 = np.sqrt(np.array([covmat[0,0] for covmat in covmatlist]))/np.sqrt(self.nslices) + err_11 = np.sqrt(np.array([covmat[1,1] for covmat in covmatlist]))/np.sqrt(self.nslices) return err_00, err_11 def observable_covariances(self, averfunc): """ input: nrm: ObservablesFromText instance + Parameters + ---------- + averfunc: + + Returns + ------- + cov_mat_fringes: + cov_mat_triples: + cov_mat_quads: + """ # loop over 21 baselines cov_mat_fringes = [] @@ -173,18 +215,30 @@ def observable_covariances(self, averfunc): covmat = self.cov_r_theta(quadamp, quadphase, averfunc) cov_mat_quads.append(covmat) - # covmats to be written to oifits. store in nrm object? - # store entire matrix... somewhere + # covmats to be written to oifits. store in rawoifits object? TBD # lists of cov mats have shape e.g. (21, 2, 2) or (35, 2, 2) + return np.array(cov_mat_fringes), np.array(cov_mat_triples), np.array(cov_mat_quads) def cov_r_theta(self, rr, theta, averfunc): """ - rr: complex number modulus, array - theta: complex number phase, array - averfunc: np.median or np.mean - rotating by **average** phase (over integrations) + Calculate covariance in x, y coordinates. Rotate covariance matrix by + **average** phase (over integrations) to get matrix in (r,theta) + + Parameters + ---------- + rr: array + complex number modulus + theta: array + complex number phase + averfunc: np.mean or np.median + + Returns + ------- + cov_mat_r_theta: array (2,2) + Covariance matrix in r, theta coordinates + """ xx = rr * np.cos(theta) yy = rr * np.sin(theta) @@ -239,7 +293,7 @@ def make_oifits(self): # Now we are setting up the observables to be written out to OIFITS if self.method not in ["mean", "median", "multi"]: - self.method = "median" + self.method = "mean" # default to mean # set these as attributes (some may exist and be overwritten) if self.method == "multi": self.vis2 = self.fringe_amplitudes_squared.T @@ -260,11 +314,11 @@ def make_oifits(self): self.e_pist = np.zeros(self.pist.shape) - elif self.method == "median": - self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.closure_phases, self.e_cp, self.t3amp, self.e_t3amp, self.camp, self.e_camp, self.q4phi, self.e_q4phi, self.pist, self.e_pist = self.average_observables(np.median) - - else: # take the mean + elif self.method == "mean": self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.closure_phases, self.e_cp, self.t3amp, self.e_t3amp, self.camp, self.e_camp, self.q4phi, self.e_q4phi, self.pist, self.e_pist = self.average_observables(np.mean) + + else: # take the median + self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.closure_phases, self.e_cp, self.t3amp, self.e_t3amp, self.camp, self.e_camp, self.q4phi, self.e_q4phi, self.pist, self.e_pist = self.average_observables(np.median) # Convert angular quantities from radians to degrees self.visphi = np.rad2deg(self.visphi) From 61d2dca28461e09ee6df68d9d9e8ed487c8308cb Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Mon, 14 Oct 2024 14:55:48 -0400 Subject: [PATCH 11/26] Fixed input args bandpass and affine2d to expect ASDF files, updated documentation with examples. --- docs/jwst/ami_analyze/description.rst | 68 ++++++++++++++++++++++- jwst/ami/ami_analyze_step.py | 80 ++++++++++++++++++++++++++- jwst/ami/utils.py | 4 -- 3 files changed, 143 insertions(+), 9 deletions(-) diff --git a/docs/jwst/ami_analyze/description.rst b/docs/jwst/ami_analyze/description.rst index c0e2d27016..abba9fb67f 100644 --- a/docs/jwst/ami_analyze/description.rst +++ b/docs/jwst/ami_analyze/description.rst @@ -36,7 +36,7 @@ other options: rotation search values. The default setting of '-3 3 1' results in search values of [-3, -2, -1, 0, 1, 2, 3]. -:--bandpass: Synphot spectrum or suitable array to override filter/source +:--bandpass: ASDF file containing suitable array to override filter/source (default=None) :--usebp: If True, exclude pixels marked DO_NOT_USE from fringe fitting @@ -47,10 +47,72 @@ other options: :--chooseholes: If not None, fit only certain fringes e.g. ['B4','B5','B6','C2'] (default=None) -:--affine2d: User-defined Affine2d object (default=None) +:--affine2d: ASDF file containing user-defined affine parameters (default=None) :--run_bpfix: Run Fourier bad pixel fix on cropped data (default=True) - + + +Creating ASDF files +^^^^^^^^^^^^^^^^^^^ +The optional arguments `bandpass` and `affine2d` must be written to `ASDF `_ +files to be used by the step. The step expects the contents to be stored with particular keys but the format is not currently +enforced by a schema; incorrect ASDF file contents will cause the step to revert back to the defaults for each argument. + +Examples of how to create ASDF files containing the properly formatted information for each of the arguments follows. + +.. code-block:: python + + # Create a F380M filter + A0V source bandpass ASDF file + + import asdf + from jwst.ami import utils + + filt='F380M' + src = 'A0V' + nspecbin=19 + + filt_spec = utils.get_filt_spec(filt) + src_spec = utils.get_src_spec(src) + + bandpass = utils.combine_src_filt(filt_spec, + src_spec, + trim=0.01, + nlambda=nspecbin) + + # this bandpass has shape (19, 2); each row is [throughput, wavelength] + + asdf_name = 'bandpass_f380m_a0v.asdf' + + tree = {"bandpass": bandpass} + + with open(asdf_name, 'wb') as fh: + af = asdf.AsdfFile(tree) + af.write_to(fh) + af.close() + + +.. code-block:: python + + # Create an affine transform ASDF file to use for the model + + import asdf + tree = { + 'mx': 1., # dimensionless x-magnification + 'my': 1., # dimensionless y-magnification + 'sx': 0., # dimensionless x shear + 'sy': 0., # dimensionless y shear + 'xo': 0., # x-offset in pupil space + 'yo': 0., # y-offset in pupil space + 'rotradccw': None } + + affineasdf = 'affine.asdf' + + with open(affineasdf, 'wb') as fh: + af = asdf.AsdfFile(tree) + af.write_to(fh) + af.close() + + Inputs ------ diff --git a/jwst/ami/ami_analyze_step.py b/jwst/ami/ami_analyze_step.py index ddf6d3fd5f..801996584c 100755 --- a/jwst/ami/ami_analyze_step.py +++ b/jwst/ami/ami_analyze_step.py @@ -2,6 +2,11 @@ from ..stpipe import Step from . import ami_analyze +from . import utils + +import numpy as np +import asdf +import synphot __all__ = ["AmiAnalyzeStep"] @@ -16,11 +21,11 @@ class AmiAnalyzeStep(Step): rotation = float(default=0.0) # Rotation initial guess [deg] psf_offset = string(default='0.0 0.0') # PSF offset values to use to create the model array rotation_search = string(default='-3 3 1') # Rotation search parameters: start, stop, step - bandpass = any(default=None) # Synphot spectrum or array to override filter/source + bandpass = string(default=None) # ASDF file containing array or Synphot spectrum to override filter/source usebp = boolean(default=True) # If True, exclude pixels marked DO_NOT_USE from fringe fitting firstfew = integer(default=None) # If not None, process only the first few integrations chooseholes = string(default=None) # If not None, fit only certain fringes e.g. ['B4','B5','B6','C2'] - affine2d = any(default=None) # None or user-defined Affine2d object + affine2d = string(default=None) # ASDF file containing user-defined affine parameters run_bpfix = boolean(default=True) # Run Fourier bad pixel fix on cropped data """ @@ -32,6 +37,68 @@ def save_model(self, model, *args, **kwargs): kwargs["suffix"] = ["ami-oi", "amimulti-oi", "amilg"][kwargs.pop("idx")] return Step.save_model(self, model, *args, **kwargs) + + def override_bandpass(self): + """ + Read bandpass from asdf file. Expects an array of [effstims, wave_m] + (i.e. np.array((effstims,wave_m)).T) stored as 'bandpass' in asdf file, + where effstims are normalized countrates (unitless) and wave_m is wavelengths + across the filter at which to compute the model (meters). + + """ + + try: + with asdf.open(self.bandpass, lazy_load=False) as af: + bandpass = np.array(af['bandpass']) + + # assume it is an array of the correct shape + wavemin = np.min(bandpass[:,1]) + wavemax = np.max(bandpass[:,1]) + self.log.info('User-defined bandpass provided:') + self.log.info('\tOVERWRITING ALL NIRISS-SPECIFIC FILTER/BANDPASS VARIABLES') + self.log.info(f'Using {bandpass.shape[0]} wavelengths for fit.') + self.log.info(f'Wavelength min: {wavemin:.3e} \t Wavelength max: {wavemax:.3e}') + + # update attribute and return + self.bandpass = bandpass + return bandpass + + except: + message = (f'Could not read bandpass from {self.bandpass}. \ + See documentation for info on creating a custom bandpass ASDF file.') + raise Exception(message) + + def override_affine2d(self): + """ + Read user-input affine transform from ASDF file. Make Affine2d object + (see utils.Affine2D class). Input should contain mx,my,sx,sy,xo,yo,rotradccw. + """ + try: + with asdf.open(self.affine2d, lazy_load=False) as af: + affine2d = utils.Affine2d( + mx = af['mx'], + my = af['my'], + sx = af['sx'], + sy = af['sy'], + xo = af['xo'], + yo = af['yo'], + rotradccw = af['rotradccw'] + ) + self.log.info('Using input affine transform with parameters:') + self.log.info(f'\tmx={af['mx']}, my={af['my']}') + self.log.info(f'\tsx={af['sx']}, sy={af['sy']}') + self.log.info(f'\txo={af['xo']}, yo={af['yo']}') + self.affine2d = affine2d + return affine2d + except: + self.log.info(f'Could not read affine transfrom parameters from {self.affine2d}. \ + See documentation for info on creating a custom affine2d ASDF file.') + self.log.info('**** DEFAULTING TO USE IDENTITY TRANSFORM ****') + affine2d = None + + self.affine2d = affine2d + return affine2d + def process(self, input): """ Performs analysis of an AMI mode exposure by applying the LG algorithm. @@ -83,6 +150,15 @@ def process(self, input): raise RuntimeError("No THROUGHPUT reference file found. " "ami_analyze cannot continue.") + # If there's a user-defined bandpass or affine, handle it + if bandpass is not None: + bandpass = self.override_bandpass() + + if affine2d is not None: + # if it is None, handled in apply_LG_plus + affine2d = self.override_affine2d() + + # Get the name of the NRM reference file to use nrm_reffile = self.get_reference_file(input_model, 'nrm') self.log.info(f'Using NRM reference file {nrm_reffile}') diff --git a/jwst/ami/utils.py b/jwst/ami/utils.py index 700f87a2c9..a9fa5e1712 100644 --- a/jwst/ami/utils.py +++ b/jwst/ami/utils.py @@ -1322,16 +1322,12 @@ def handle_bandpass(bandpass, throughput_model): Array of weights, wavelengths used to generate model """ if bandpass is not None: - log.info( - "User-defined bandpass provided: OVERWRITING ALL NIRISS-SPECIFIC FILTER/BANDPASS VARIABLES" - ) # bandpass can be user-defined synphot object or appropriate array if isinstance(bandpass, synphot.spectrum.SpectralElement): log.info("User-defined synphot spectrum provided") wl, wt = bandpass._get_arrays(bandpass.waveset) bandpass = np.array((wt, wl)).T else: - log.info("User-defined bandpass array provided") bandpass = np.array(bandpass) else: From 2267535cffa42d8f7f4835d4a490488a22e89fb6 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Mon, 18 Nov 2024 11:47:01 -0500 Subject: [PATCH 12/26] point pyproject requirements to stdatamodels branch adding pa, calib keywords --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6d61c69b5c..a12ab45eec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "scipy>=1.9.3", "spherical-geometry>=1.2.22", "stcal @ git+https://github.com/spacetelescope/stcal.git@main", - "stdatamodels @ git+https://github.com/spacetelescope/stdatamodels.git@main", + "stdatamodels @ git+https://github.com/braingram/stdatamodels.git@calib_pa", "stpipe @ git+https://github.com/spacetelescope/stpipe.git@main", "stsci.imagestats>=1.6.3", "synphot>=1.2", From cdcee17bbb358df7d03af7813b5bbe635571f5a7 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Mon, 18 Nov 2024 19:44:43 -0500 Subject: [PATCH 13/26] Carry wcsinfo from input through to ami-oi output, correctly populate calibrator_object_id in calibrated files. Use ROLL_REF instead of PA_V3, though leaving old variable names for now. --- jwst/ami/instrument_data.py | 11 ++++++++++- jwst/ami/oifits.py | 7 +++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/jwst/ami/instrument_data.py b/jwst/ami/instrument_data.py index 7b5e11614d..e0d963d519 100755 --- a/jwst/ami/instrument_data.py +++ b/jwst/ami/instrument_data.py @@ -179,9 +179,18 @@ def read_data_model(self, input_model): pscale_deg = np.mean([pscaledegx, pscaledegy]) self.pscale_rad = np.deg2rad(pscale_deg) self.pscale_mas = pscale_deg * (60 * 60 * 1000) - self.pav3 = input_model.meta.pointing.pa_v3 + ##### + + # Until further notice this is fine + #self.pav3 = input_model.meta.pointing.pa_v3 + self.pav3 = input_model.meta.wcsinfo.roll_ref + self.vparity = input_model.meta.wcsinfo.vparity self.v3iyang = input_model.meta.wcsinfo.v3yangle + + self.wcsinfo = input_model.meta.wcsinfo + + self.parangh = input_model.meta.wcsinfo.roll_ref self.crpix1 = input_model.meta.wcsinfo.crpix1 self.crpix2 = input_model.meta.wcsinfo.crpix2 diff --git a/jwst/ami/oifits.py b/jwst/ami/oifits.py index ed9f342429..6841da21a5 100644 --- a/jwst/ami/oifits.py +++ b/jwst/ami/oifits.py @@ -48,9 +48,6 @@ def __init__(self, fringefitter, method="mean"): self.ctrs_eqt = self.fringe_fitter.instrument_data.ctrs_eqt self.ctrs_inst = self.fringe_fitter.instrument_data.ctrs_inst - self.pa = ( - self.fringe_fitter.instrument_data.pav3 - ) # header pav3, not including v3i_yang?? self.bholes, self.bls = self._makebaselines() self.tholes, self.tuv = self._maketriples_all() @@ -365,6 +362,8 @@ def make_oifits(self): m.meta.oifits.array_name = instrument_data.arrname m.meta.oifits.instrument_mode = instrument_data.pupil + m.meta.wcsinfo = instrument_data.wcsinfo + # oi_array extension data m.array["TEL_NAME"] = tel_name m.array["STA_NAME"] = sta_name @@ -770,6 +769,6 @@ def calibrate(self): # add calibrated header keywords calname = self.caloimodel.meta.target.proposer_name # name of calibrator star - self.calib_oimodel.meta.oifits.calib = calname + self.calib_oimodel.meta.calibrator_object_id = calname return self.calib_oimodel From ac86ba1157a645db99559bc7867a0df514d9c708 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Wed, 20 Nov 2024 15:36:31 -0500 Subject: [PATCH 14/26] update affine log message --- jwst/ami/ami_analyze_step.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/jwst/ami/ami_analyze_step.py b/jwst/ami/ami_analyze_step.py index 801996584c..b1d6000a7c 100755 --- a/jwst/ami/ami_analyze_step.py +++ b/jwst/ami/ami_analyze_step.py @@ -84,10 +84,8 @@ def override_affine2d(self): yo = af['yo'], rotradccw = af['rotradccw'] ) - self.log.info('Using input affine transform with parameters:') - self.log.info(f'\tmx={af['mx']}, my={af['my']}') - self.log.info(f'\tsx={af['sx']}, sy={af['sy']}') - self.log.info(f'\txo={af['xo']}, yo={af['yo']}') + self.log.info(f'Using affine transform from ASDF file {self.affine2d}') + # now self.affine2d updated from string to object self.affine2d = affine2d return affine2d except: From 2190158a895c3b44e74119eb4a4323bb4f362b3f Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Wed, 20 Nov 2024 17:42:31 -0500 Subject: [PATCH 15/26] removed unused synphot import --- jwst/ami/ami_analyze_step.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jwst/ami/ami_analyze_step.py b/jwst/ami/ami_analyze_step.py index b1d6000a7c..3eaf7fb377 100755 --- a/jwst/ami/ami_analyze_step.py +++ b/jwst/ami/ami_analyze_step.py @@ -6,7 +6,6 @@ import numpy as np import asdf -import synphot __all__ = ["AmiAnalyzeStep"] @@ -21,7 +20,7 @@ class AmiAnalyzeStep(Step): rotation = float(default=0.0) # Rotation initial guess [deg] psf_offset = string(default='0.0 0.0') # PSF offset values to use to create the model array rotation_search = string(default='-3 3 1') # Rotation search parameters: start, stop, step - bandpass = string(default=None) # ASDF file containing array or Synphot spectrum to override filter/source + bandpass = string(default=None) # ASDF file containing array to override filter/source usebp = boolean(default=True) # If True, exclude pixels marked DO_NOT_USE from fringe fitting firstfew = integer(default=None) # If not None, process only the first few integrations chooseholes = string(default=None) # If not None, fit only certain fringes e.g. ['B4','B5','B6','C2'] From 9ac6b5f9c69ebe283d9f30e39d6ad1f601c83819 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Fri, 22 Nov 2024 12:04:26 -0500 Subject: [PATCH 16/26] update tests to use roll_ref, correct throughput reffile error message --- jwst/ami/tests/test_ami_interface.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jwst/ami/tests/test_ami_interface.py b/jwst/ami/tests/test_ami_interface.py index 2da7d16eb3..23e765a343 100644 --- a/jwst/ami/tests/test_ami_interface.py +++ b/jwst/ami/tests/test_ami_interface.py @@ -48,7 +48,7 @@ def example_model(mock_nrm_reference_file): model.meta.program.pi_name = "someone" model.meta.target.catalog_name = "" model.meta.visit.start_time = "2022-06-05 12:15:41.5020000" - model.meta.pointing.pa_v3 = 171.8779402866089 + model.meta.wcsinfo.roll_ref = 171.8779402866089 model.meta.wcsinfo.v3yangle = 0.56126717 model.meta.filename = "test_calints.fits" model.meta.instrument.pupil = "NRM" @@ -63,7 +63,6 @@ def test_ami_analyze_even_oversample_fail(example_model, oversample): AmiAnalyzeStep.call(example_model, oversample=oversample) -@pytest.mark.skip("reference files are not currently used") def test_ami_analyze_no_reffile_fail(monkeypatch, example_model): """Make sure that ami_analyze fails if no throughput reffile is available""" @@ -71,7 +70,7 @@ def mockreturn(input_model, reftype, observatory=None, asn_exptypes=None): return "N/A" monkeypatch.setattr(stpipe.crds_client, 'get_reference_file', mockreturn) - with pytest.raises(RuntimeError, match="No throughput reference file found."): + with pytest.raises(RuntimeError, match="No THROUGHPUT reference file found."): AmiAnalyzeStep.call(example_model) From 3c3a00fc7d6a9fb042cb30391c80c3317ad3c3ac Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Fri, 22 Nov 2024 14:18:55 -0500 Subject: [PATCH 17/26] changelog file added --- changes/8846.ami.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/8846.ami.rst diff --git a/changes/8846.ami.rst b/changes/8846.ami.rst new file mode 100644 index 0000000000..a965c823ff --- /dev/null +++ b/changes/8846.ami.rst @@ -0,0 +1 @@ +Change how AMI observables are averaged: average fringe quantities before calculating additional observables. Update their error calculation: use covariance of amplitudes/phases (and derived quantities) and standard error of the mean. Code now expects an ASDF filename string for user-supplied affine2d and bandpass input arguments. Example file creation in documentation. \ No newline at end of file From 1a806caa338d653dc3832687765fb86b9d02304b Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Mon, 2 Dec 2024 16:14:40 -0500 Subject: [PATCH 18/26] Updates to use latest changes to stdatamodels AmiOIModel to store position angle related keywords --- jwst/ami/instrument_data.py | 26 ++++++++++++-------------- jwst/ami/oifits.py | 6 ++++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/jwst/ami/instrument_data.py b/jwst/ami/instrument_data.py index e0d963d519..7315a09d3a 100755 --- a/jwst/ami/instrument_data.py +++ b/jwst/ami/instrument_data.py @@ -179,19 +179,12 @@ def read_data_model(self, input_model): pscale_deg = np.mean([pscaledegx, pscaledegy]) self.pscale_rad = np.deg2rad(pscale_deg) self.pscale_mas = pscale_deg * (60 * 60 * 1000) - ##### - - # Until further notice this is fine - #self.pav3 = input_model.meta.pointing.pa_v3 - self.pav3 = input_model.meta.wcsinfo.roll_ref + self.roll_ref = input_model.meta.wcsinfo.roll_ref self.vparity = input_model.meta.wcsinfo.vparity self.v3iyang = input_model.meta.wcsinfo.v3yangle - self.wcsinfo = input_model.meta.wcsinfo - - - self.parangh = input_model.meta.wcsinfo.roll_ref + self.parangh = self.roll_ref self.crpix1 = input_model.meta.wcsinfo.crpix1 self.crpix2 = input_model.meta.wcsinfo.crpix2 self.pupil = input_model.meta.instrument.pupil @@ -232,7 +225,7 @@ def read_data_model(self, input_model): log.warning("All integrations will be analyzed") self.nwav = scidata.shape[0] [self.wls.append(self.wls[0]) for f in range(self.nwav - 1)] - # Rotate mask hole centers by pav3 + v3i_yang to be in equatorial coordinates + # Rotate mask hole centers by roll_ref + v3i_yang to be in equatorial coordinates ctrs_sky = self.mast2sky() oifctrs = np.zeros(self.mask.ctrs.shape) oifctrs[:, 0] = ctrs_sky[:, 1].copy() * -1 @@ -341,10 +334,15 @@ def reset_nwav(self, nwav): def mast2sky(self): """ Rotate hole center coordinates: - Clockwise by the V3 position angle - V3I_YANG from north in degrees if VPARITY = -1 - Counterclockwise by the V3 position angle - V3I_YANG from north in degrees if VPARITY = 1 + Clockwise by the ROLL_REF + V3I_YANG from north in degrees if VPARITY = -1 + Counterclockwise by the ROLL_REF + V3I_YANG from north in degrees if VPARITY = 1 Hole center coords are in the V2, V3 plane in meters. + Notes + ----- + Nov. 2024 email discussion with Tony Sohn, Paul Goudfrooij confirmed V2/V3 coordinate + rotation back to "North up" equatorial orientation should use ROLL_REF + V3I_YANG + (= PA_APER). Returns ------- ctrs_rot: array @@ -356,9 +354,9 @@ def mast2sky(self): # NOT used for the fringe fitting itself mask_ctrs = utils.rotate2dccw(mask_ctrs, np.pi / 2.0) vpar = self.vparity # Relative sense of rotation between Ideal xy and V2V3 - rot_ang = self.pav3 - self.v3iyang # subject to change! + rot_ang = self.roll_ref + self.v3iyang # subject to change! - if self.pav3 == 0.0: + if self.roll_ref == 0.0: return mask_ctrs else: # Using rotate2sccw, which rotates **vectors** CCW in a fixed coordinate system, diff --git a/jwst/ami/oifits.py b/jwst/ami/oifits.py index 6841da21a5..11ed3d1dcc 100644 --- a/jwst/ami/oifits.py +++ b/jwst/ami/oifits.py @@ -362,7 +362,9 @@ def make_oifits(self): m.meta.oifits.array_name = instrument_data.arrname m.meta.oifits.instrument_mode = instrument_data.pupil - m.meta.wcsinfo = instrument_data.wcsinfo + m.meta.ami.roll_ref = instrument_data.roll_ref + m.meta.ami.v3yangle = instrument_data.v3iyang + m.meta.ami.vparity = instrument_data.vparity # oi_array extension data m.array["TEL_NAME"] = tel_name @@ -769,6 +771,6 @@ def calibrate(self): # add calibrated header keywords calname = self.caloimodel.meta.target.proposer_name # name of calibrator star - self.calib_oimodel.meta.calibrator_object_id = calname + self.calib_oimodel.meta.ami.calibrator_object_id = calname return self.calib_oimodel From 99ad0f3b381c9a5064de5dfef071eb95619a7c09 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Tue, 3 Dec 2024 14:56:45 -0500 Subject: [PATCH 19/26] small docs updates --- docs/jwst/ami_analyze/description.rst | 9 ++++----- docs/jwst/pipeline/calwebb_ami3.rst | 15 +++++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/jwst/ami_analyze/description.rst b/docs/jwst/ami_analyze/description.rst index abba9fb67f..bad45c8c63 100644 --- a/docs/jwst/ami_analyze/description.rst +++ b/docs/jwst/ami_analyze/description.rst @@ -132,7 +132,7 @@ The ``ami_analyze`` step itself does not accept an ASN as input. Outputs ------- -The ``ami_analyze`` step produces three output files. The first two (``_ami-oi.fits`` and ``_amimulti-oi.fits``) contain the interferometric observables, and the third (``_amilg.fits``) contains the data, LG model, and residuals. These are described in more detail below. +The ``ami_analyze`` step produces three output files. The first two (``_ami-oi.fits`` and ``_amimulti-oi.fits``) contain the interferometric observables, and the third (``_amilg.fits``) contains the data, LG model, and residuals. All products contain a ``PRIMARY`` HDU containing header keywords but no science data, as well as an ``ASDF`` extension. The files are described in more detail below. The output file name syntax is exposure-based, using the input file name as the root, with the addition of the association candidate ID and the "_ami-oi", "_amimulti-oi", or "amilg" product type suffix, e.g. @@ -143,20 +143,19 @@ Interferometric observables :Data model: `~jwst.datamodels.AmiOIModel` :File suffix: _ami-oi.fits, _amimulti-oi.fits -. The inteferometric observables are saved as OIFITS files, a registered FITS format +The inteferometric observables are saved as OIFITS files, a registered FITS format for optical interferometry, containing the following list of extensions: 1) ``OI_ARRAY``: AMI subaperture information 2) ``OI_TARGET``: target properties -3) ``OI_T3``: extracted closure amplitudes, phases +3) ``OI_T3``: extracted closure amplitudes, triple-product phases 4) ``OI_VIS``: extracted visibility (fringe) amplitudes, phases 5) ``OI_VIS2``: squared visibility (fringe) amplitudes 6) ``OI_WAVELENGTH``: filter information For more information on the format and contents of OIFITS files, see the `OIFITS2 standard `_. -The _ami-oi.fits file contains tables of median observables over all integrations of the input file. Errors -are computed as the sigma-clipped standard deviation over integrations. +The _ami-oi.fits file contains tables of observables averaged over all integrations of the input file. The error is taken to be the standard error of the mean, where the variance is the covariance between amplitudes and phases (e.g. fringe amplitudes and fringe phases, closure phases and triple-product amplitudes). The _amimulti-oi.fits file contains observables for each integration, and does not contain error estimates. The structure is the same as the _ami-oi.fits file, but the following data columns are 2D, with the second dimension being the number of integrations: "PISTONS", "PIST_ERR", "VISAMP", "VISAMPERR", "VISPHI", "VISPHIERR", "VIS2DATA", "VIS2ERR", "T3AMP", "T3AMPERR", "T3PHI", "T3PHIERR". diff --git a/docs/jwst/pipeline/calwebb_ami3.rst b/docs/jwst/pipeline/calwebb_ami3.rst index c3f494861f..d8aeb69cbc 100644 --- a/docs/jwst/pipeline/calwebb_ami3.rst +++ b/docs/jwst/pipeline/calwebb_ami3.rst @@ -7,8 +7,7 @@ calwebb_ami3: Stage 3 Aperture Masking Interferometry (AMI) Processing :Alias: calwebb_ami3 The stage 3 AMI pipeline is applied to associations of calibrated NIRISS AMI exposures. -It computes fringe parameters for individual exposures, averages the fringe results from -multiple exposures, and, optionally, corrects science target fringe parameters using the +It computes fringe parameters for individual exposures, and, optionally, corrects science target fringe parameters using the fringe results from reference PSF targets. The steps applied by the ``calwebb_ami3`` pipeline are shown below. @@ -83,15 +82,14 @@ Interferometric observables :Data model: `~jwst.datamodels.AmiOIModel` :File suffix: _ami-oi.fits - -For every input exposure, the fringe parameters and closure phases calculated +For every input exposure, the interferometric observables calculated by the :ref:`ami_analyze ` step are saved to an "_ami-oi.fits" product file, -which is a FITS table of median observables over all integrations of the input file. +which is a FITS table of averaged observables over all integrations of the input file. Product names use the input "_calints" exposure-based file name, with the association candidate ID included and the product type changed to "_ami-oi.fits", e.g. "jw93210001001_03101_00001_nis_a0003_ami-oi.fits." -Normalized Interferometric Observables +Normalized interferometric observables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :Data model: `~jwst.datamodels.AmiOIModel` :File suffix: _aminorm-oi.fits @@ -102,3 +100,8 @@ via the :ref:`ami_normalize ` step, and will be saved to an product file. This file has the same FITS table format as the "_ami-oi.fits" products. The file name root uses the source-based output product name given in the ASN file, e.g. "jw93210-a0003_t001_niriss_f480m-nrm_aminorm-oi.fits." + +.. note:: + + Users may wish to run the :ref:`ami_analyze step ` separately for access to flexible input arguments and to save additional diagnostic output products. See the step documentation for more details. + From b64f3a0023f86812a7010fb520d9ebb8d19daba1 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Wed, 4 Dec 2024 13:51:55 -0500 Subject: [PATCH 20/26] stdatamodels requirement back to main --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a12ab45eec..6d61c69b5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "scipy>=1.9.3", "spherical-geometry>=1.2.22", "stcal @ git+https://github.com/spacetelescope/stcal.git@main", - "stdatamodels @ git+https://github.com/braingram/stdatamodels.git@calib_pa", + "stdatamodels @ git+https://github.com/spacetelescope/stdatamodels.git@main", "stpipe @ git+https://github.com/spacetelescope/stpipe.git@main", "stsci.imagestats>=1.6.3", "synphot>=1.2", From 7745ad29d4459d2045a3e9b670ccab07e8803cb1 Mon Sep 17 00:00:00 2001 From: Rachel Cooper Date: Fri, 6 Dec 2024 13:26:41 -0500 Subject: [PATCH 21/26] Resolving issues and suggestions from PR reviews by @melanieclarke and @emolter --- docs/jwst/ami_analyze/description.rst | 45 +++-- jwst/ami/ami_analyze_step.py | 58 ++++-- jwst/ami/instrument_data.py | 12 +- jwst/ami/leastsqnrm.py | 24 +-- jwst/ami/nrm_core.py | 2 - jwst/ami/oifits.py | 281 ++++++++++++++++---------- 6 files changed, 255 insertions(+), 167 deletions(-) diff --git a/docs/jwst/ami_analyze/description.rst b/docs/jwst/ami_analyze/description.rst index bad45c8c63..26b2f597ff 100644 --- a/docs/jwst/ami_analyze/description.rst +++ b/docs/jwst/ami_analyze/description.rst @@ -62,33 +62,33 @@ Examples of how to create ASDF files containing the properly formatted informati .. code-block:: python - # Create a F380M filter + A0V source bandpass ASDF file + # Create a F480M filter + Vega bandpass ASDF file import asdf from jwst.ami import utils + from stdatamodels.jwst import datamodels + from synphot import SourceSpectrum - filt='F380M' - src = 'A0V' + # F480M throughput reference file from JWST CRDS + throughput_file = 'jwst_niriss_throughput_0012.fits' nspecbin=19 + throughput_model = datamodels.open(throughput_file) - filt_spec = utils.get_filt_spec(filt) - src_spec = utils.get_src_spec(src) - - bandpass = utils.combine_src_filt(filt_spec, - src_spec, - trim=0.01, - nlambda=nspecbin) - - # this bandpass has shape (19, 2); each row is [throughput, wavelength] - - asdf_name = 'bandpass_f380m_a0v.asdf' + filt_spec = utils.get_filt_spec(throughput_model) + src_spec = SourceSpectrum.from_vega() + bandpass = utils.combine_src_filt(filt_spec, + src_spec, + trim=0.01, + nlambda=nspecbin) + # This bandpass has shape (19, 2); each row is [throughput, wavelength] + asdf_name = 'bandpass_f480m_vega.asdf' tree = {"bandpass": bandpass} - with open(asdf_name, 'wb') as fh: af = asdf.AsdfFile(tree) af.write_to(fh) af.close() + throughput_model.close() .. code-block:: python @@ -97,13 +97,14 @@ Examples of how to create ASDF files containing the properly formatted informati import asdf tree = { - 'mx': 1., # dimensionless x-magnification - 'my': 1., # dimensionless y-magnification - 'sx': 0., # dimensionless x shear - 'sy': 0., # dimensionless y shear - 'xo': 0., # x-offset in pupil space - 'yo': 0., # y-offset in pupil space - 'rotradccw': None } + 'mx': 1., # dimensionless x-magnification + 'my': 1., # dimensionless y-magnification + 'sx': 0., # dimensionless x shear + 'sy': 0., # dimensionless y shear + 'xo': 0., # x-offset in pupil space + 'yo': 0., # y-offset in pupil space + 'rotradccw': None + } affineasdf = 'affine.asdf' diff --git a/jwst/ami/ami_analyze_step.py b/jwst/ami/ami_analyze_step.py index 3eaf7fb377..3330e6aa25 100755 --- a/jwst/ami/ami_analyze_step.py +++ b/jwst/ami/ami_analyze_step.py @@ -36,14 +36,19 @@ def save_model(self, model, *args, **kwargs): kwargs["suffix"] = ["ami-oi", "amimulti-oi", "amilg"][kwargs.pop("idx")] return Step.save_model(self, model, *args, **kwargs) - def override_bandpass(self): """ - Read bandpass from asdf file. Expects an array of [effstims, wave_m] - (i.e. np.array((effstims,wave_m)).T) stored as 'bandpass' in asdf file, - where effstims are normalized countrates (unitless) and wave_m is wavelengths - across the filter at which to compute the model (meters). + Read bandpass from asdf file and use it to override the default. + Expects an array of [effstims, wave_m] + (i.e. np.array((effstims,wave_m)).T) stored as 'bandpass' in asdf file, + where effstims are normalized countrates (unitless) and wave_m are the + wavelengths across the filter at which to compute the model (meters). + + Returns + ------- + bandpass: array + Array of [countrates, wavelengths] """ try: @@ -62,15 +67,26 @@ def override_bandpass(self): self.bandpass = bandpass return bandpass - except: - message = (f'Could not read bandpass from {self.bandpass}. \ - See documentation for info on creating a custom bandpass ASDF file.') + except FileNotFoundError: + message = f'File {self.bandpass} could not be found at the specified location.' raise Exception(message) + except KeyError: + message1 = 'ASDF file does not contain the required "bandpass" key. ' + message2 = 'See step documentation for info on creating a custom bandpass ASDF file.' + raise Exception((message1 + message2)) + + except (IndexError, ValueError): + message1 = f'Could not use bandpass from {self.bandpass}. It may have the wrong shape. ' + message2 = 'See documentation for info on creating a custom bandpass ASDF file.' + raise Exception((message1 + message2)) + def override_affine2d(self): """ - Read user-input affine transform from ASDF file. Make Affine2d object - (see utils.Affine2D class). Input should contain mx,my,sx,sy,xo,yo,rotradccw. + Read user-input affine transform from ASDF file. + + Makes an Affine2d object (see utils.Affine2D class). + Input should contain mx,my,sx,sy,xo,yo,rotradccw. """ try: with asdf.open(self.affine2d, lazy_load=False) as af: @@ -87,10 +103,24 @@ def override_affine2d(self): # now self.affine2d updated from string to object self.affine2d = affine2d return affine2d - except: - self.log.info(f'Could not read affine transfrom parameters from {self.affine2d}. \ - See documentation for info on creating a custom affine2d ASDF file.') - self.log.info('**** DEFAULTING TO USE IDENTITY TRANSFORM ****') + + except FileNotFoundError: + self.log.info(f'File {self.affine2d} could not be found at the specified location.') + self.log.info('\t **** DEFAULTING TO USE IDENTITY TRANSFORM ****') + affine2d = None + + except KeyError: + message1 = 'ASDF file does not contain all of the required keys: mx, my, sx, sy ,xo, yo, rotradccw. ' + message2 = 'See step documentation for info on creating a custom affine2d ASDF file.' + self.log.info((message1 + message2)) + self.log.info('\t **** DEFAULTING TO USE IDENTITY TRANSFORM ****') + affine2d = None + + except (IndexError, TypeError, ValueError): + message1 = f'Could not use affine2d from {self.affine2d}. ' + message2 = 'See documentation for info on creating a custom bandpass ASDF file.' + self.log.info((message1 + message2)) + self.log.info('\t **** DEFAULTING TO USE IDENTITY TRANSFORM ****') affine2d = None self.affine2d = affine2d diff --git a/jwst/ami/instrument_data.py b/jwst/ami/instrument_data.py index 7315a09d3a..26d4f73d08 100755 --- a/jwst/ami/instrument_data.py +++ b/jwst/ami/instrument_data.py @@ -184,7 +184,6 @@ def read_data_model(self, input_model): self.vparity = input_model.meta.wcsinfo.vparity self.v3iyang = input_model.meta.wcsinfo.v3yangle - self.parangh = self.roll_ref self.crpix1 = input_model.meta.wcsinfo.crpix1 self.crpix2 = input_model.meta.wcsinfo.crpix2 self.pupil = input_model.meta.instrument.pupil @@ -333,7 +332,9 @@ def reset_nwav(self, nwav): def mast2sky(self): """ - Rotate hole center coordinates: + Rotate hole center coordinates. + + Rotation of coordinates is: Clockwise by the ROLL_REF + V3I_YANG from north in degrees if VPARITY = -1 Counterclockwise by the ROLL_REF + V3I_YANG from north in degrees if VPARITY = 1 Hole center coords are in the V2, V3 plane in meters. @@ -342,7 +343,8 @@ def mast2sky(self): ----- Nov. 2024 email discussion with Tony Sohn, Paul Goudfrooij confirmed V2/V3 coordinate rotation back to "North up" equatorial orientation should use ROLL_REF + V3I_YANG - (= PA_APER). + (= PA_APER). + Returns ------- ctrs_rot: array @@ -354,13 +356,13 @@ def mast2sky(self): # NOT used for the fringe fitting itself mask_ctrs = utils.rotate2dccw(mask_ctrs, np.pi / 2.0) vpar = self.vparity # Relative sense of rotation between Ideal xy and V2V3 - rot_ang = self.roll_ref + self.v3iyang # subject to change! + rot_ang = self.roll_ref + self.v3iyang if self.roll_ref == 0.0: return mask_ctrs else: # Using rotate2sccw, which rotates **vectors** CCW in a fixed coordinate system, - # so to rotate coord system CW instead of the vector, reverse sign of rotation angle. Double-check comment + # so to rotate coord system CW instead of the vector, reverse sign of rotation angle. if vpar == -1: # rotate clockwise ctrs_rot = utils.rotate2dccw(mask_ctrs, np.deg2rad(-rot_ang)) diff --git a/jwst/ami/leastsqnrm.py b/jwst/ami/leastsqnrm.py index f7894278e3..79983d34e7 100644 --- a/jwst/ami/leastsqnrm.py +++ b/jwst/ami/leastsqnrm.py @@ -746,8 +746,8 @@ def populate_antisymmphasearray(deltaps, n=7): deltaps: 1D float array pistons between each pair of holes - n: integer - number of holes + n: integer, optional + number of holes (default=7) Returns ------- @@ -778,8 +778,8 @@ def populate_symmamparray(amps, n=7): amps: 1D float array fringe visibility between each pair of holes - n: integer - number of holes + n: integer, optional + number of holes (default=7) Returns ------- @@ -810,8 +810,8 @@ def t3_amplitudes(amps, n=7): amps: 1D float array fringe visibility between each pair of holes - n: integer - number of holes + n: integer, optional + number of holes (default=7) Returns ------- @@ -847,8 +847,8 @@ def redundant_cps(deltaps, n=7): deltaps: 1D float array pistons between each pair of holes - n: integer - number of holes + n: integer, optional + number of holes (default=7) Returns ------- @@ -943,8 +943,8 @@ def closure_amplitudes(amps, n=7): amps: 1D float array fringe amplitudes - N: integer - number of holes + n: integer, optional + number of holes (default=7) Returns ------- @@ -981,8 +981,8 @@ def q4_phases(deltaps, n=7): deltaps: 1D float array pistons between each pair of holes - n: integer - number of holes + n: integer, optional + number of holes (default=7) Returns ------- diff --git a/jwst/ami/nrm_core.py b/jwst/ami/nrm_core.py index 8b8f310096..e8e54b68f7 100755 --- a/jwst/ami/nrm_core.py +++ b/jwst/ami/nrm_core.py @@ -234,5 +234,3 @@ def fit_fringes_single_integration(self, slc): # model now stored as nrm.modelpsf, also nrm.residual self.nrm = nrm # this gets updated with each slice return nrm # to fit_fringes_all, where the output model will be created from list of nrm objects - - diff --git a/jwst/ami/oifits.py b/jwst/ami/oifits.py index 11ed3d1dcc..b373babd24 100644 --- a/jwst/ami/oifits.py +++ b/jwst/ami/oifits.py @@ -8,14 +8,17 @@ CalibOifits class: takes two AmiOIModel datamodels and produces a final calibrated datamodel. """ import numpy as np - from scipy.special import comb from astropy.stats import sigma_clipped_stats from astropy.time.core import Time +import logging from stdatamodels.jwst import datamodels from . import leastsqnrm +log = logging.getLogger(__name__) +log.setLevel(logging.DEBUG) + class RawOifits: def __init__(self, fringefitter, method="mean"): @@ -45,6 +48,9 @@ def __init__(self, fringefitter, method="mean"): self.n_closure_amplitudes = int(comb(self.n_holes, 4)) # also 35 self.method = method + if self.method not in ["mean", "median", "multi"]: + log.warning('"method" for saving OIFITS file must be one of ["mean","median","multi"]. Defaulting to use mean!') + self.method = "mean" self.ctrs_eqt = self.fringe_fitter.instrument_data.ctrs_eqt self.ctrs_inst = self.fringe_fitter.instrument_data.ctrs_inst @@ -87,24 +93,28 @@ def rotate_matrix(self, cov_mat, theta): Parameters ---------- - cov_mat: + cov_mat: array + The matrix to be rotated theta: float Angle by which to rotate the matrix (radians) Returns ------- + cv_rotated: array + The rotated matrix """ c, s = np.cos(theta), np.sin(theta) - R_mat = [[c, -s], + r_mat = [[c, -s], [s, c]] # coordinate rotation from real/imaginary to absolute value/phase (modulus/argument) - cv_rotated = np.linalg.multi_dot([np.transpose(R_mat), cov_mat, R_mat]) - return cv_rotated + return np.linalg.multi_dot([np.transpose(r_mat), cov_mat, r_mat]) def average_observables(self, averfunc): """ + Average all the observables. + Calculate covariance matrices between fringe amplitudes/fringe phases, and between triple product amps/closure phases, and closure amplitudes/quad phases. Convert r, theta (modulus, phase) to x,y. Calculate cov(x,y). Rotate resulting @@ -113,11 +123,43 @@ def average_observables(self, averfunc): Parameters ---------- - averfunc + averfunc: function + np.mean (default) or np.median Returns ------- - avg_sqv, err_sqv, avg_fa, err_fa, avg_fp, err_fp, avg_cp, err_cp, avg_t3amp, err_t3amp, avg_ca, err_ca, avg_q4phi, err_q4phi, avg_pist, err_pist + avg_sqv: array + Averaged squared visibilites + err_sqv: array + Standard error of the mean of averaged squared visibilities + avg_fa: array + Averaged fringe (visibility) amplitudes + err_fa: array + Standard error of the mean of averaged fringe (visibility) amplitudes + avg_fp: array + Averaged fringe phases (rad) + err_fp: array + Standard error of the mean of averaged fringe phases (rad) + avg_cp: array + Averaged closure phases (rad) + err_cp: array + Standard error of the mean of averaged closure phases (rad) + avg_t3amp: array + Averaged triple amplitudes + err_t3amp: array + Standard error of the mean of averaged triple amplitudes + avg_ca: array + Averaged closure amplitudes + err_ca: array + Standard error of the mean of averaged closure amplitudes + avg_q4phi: array + Averaged quad phases + err_q4phi: array + Standard error of the mean of averaged quad phases + avg_pist: array + Averaged segment pistons + err_pist: array + Standard error of the mean of averaged segment pistons """ covmats_fringes, covmats_triples, covmats_quads = self.observable_covariances(averfunc) @@ -148,14 +190,19 @@ def average_observables(self, averfunc): avg_q4phi = leastsqnrm.q4_phases(avg_fp, n=self.n_holes) err_ca, err_q4phi = self.err_from_covmat(covmats_quads) - return avg_sqv, err_sqv, avg_fa, err_fa, avg_fp, err_fp, avg_cp, err_cp, avg_t3amp, err_t3amp, avg_ca, err_ca, avg_q4phi, err_q4phi, avg_pist, err_pist + return (avg_sqv, err_sqv, avg_fa, err_fa, + avg_fp, err_fp, avg_cp, err_cp, + avg_t3amp, err_t3amp, avg_ca, err_ca, + avg_q4phi, err_q4phi, avg_pist, err_pist) def err_from_covmat(self, covmatlist): """ + Derive observable errors from their covariance matrices. + Return sqrt of [0,0] and [1,1] elements of each of a list of covariance matrices, - divided by sqrt(N_ints), for use as observable errors. (standard error of the mean) + divided by sqrt(N_ints), for use as observable errors (standard error of the mean). If using median, error calculation is questionable because this is NOT the standard - error of the median + error of the median. Parameters ---------- @@ -177,21 +224,29 @@ def err_from_covmat(self, covmatlist): def observable_covariances(self, averfunc): """ - input: nrm: ObservablesFromText instance + Calculate covariance matrices from each pair of observables. + + For each baseline/triple/quad, calculate covariance between each + fringe amplitude/phase quantity. + Parameters ---------- - averfunc: + averfunc: function + np.mean (default) or np.median Returns ------- - cov_mat_fringes: - cov_mat_triples: - cov_mat_quads: + cov_mat_fringes: array + Array of 21 covariance matrices for fringes (amplitudes, phases) + cov_mat_triples: array + Array of 35 covariance matrices for triples (t3 amplitudes, closure phases) + cov_mat_quads: array + Array of 35 covariance matrices for quads (closure amplitudes, quad phases) """ # loop over 21 baselines cov_mat_fringes = [] - # these currently operate on all slices at once. other one operated on already-averaged slices + # These operate on all slices at once, not on already-averaged integrations for bl in np.arange(self.n_baselines): fringeamps = self.fringe_amplitudes[:,bl] fringephases = self.fringe_phases[:,bl] @@ -220,8 +275,10 @@ def observable_covariances(self, averfunc): def cov_r_theta(self, rr, theta, averfunc): """ - Calculate covariance in x, y coordinates. Rotate covariance matrix by - **average** phase (over integrations) to get matrix in (r,theta) + Calculate covariance in polar coordinates. + + Calculate covariance in x, y coordinates, then rotate covariance matrix + by **average** phase (over integrations) to get matrix in (r,theta). Parameters ---------- @@ -229,7 +286,8 @@ def cov_r_theta(self, rr, theta, averfunc): complex number modulus theta: array complex number phase - averfunc: np.mean or np.median + averfunc: function + np.mean (default) or np.median Returns ------- @@ -240,8 +298,7 @@ def cov_r_theta(self, rr, theta, averfunc): xx = rr * np.cos(theta) yy = rr * np.sin(theta) cov_mat_xy = np.cov(xx, yy) - cov_mat_r_theta = self.rotate_matrix(cov_mat_xy, averfunc(theta)) - return cov_mat_r_theta + return self.rotate_matrix(cov_mat_xy, averfunc(theta)) def make_oifits(self): @@ -279,18 +336,14 @@ def make_oifits(self): flagVis = [False] * self.n_baselines flagT3 = [False] * self.n_closure_phases - # do the things done by populate_nrm here - # average or don't, and get uncertainties + # Average observables (or don't), and get uncertainties # Unwrap phases shift2pi = np.zeros(self.closure_phases.shape) shift2pi[self.closure_phases >= 6] = 2 * np.pi shift2pi[self.closure_phases <= -6] = -2 * np.pi self.closure_phases -= shift2pi - # this is problematic because phases were converted to DEGREES in make_obsarrays()? # Now we are setting up the observables to be written out to OIFITS - if self.method not in ["mean", "median", "multi"]: - self.method = "mean" # default to mean # set these as attributes (some may exist and be overwritten) if self.method == "multi": self.vis2 = self.fringe_amplitudes_squared.T @@ -312,10 +365,16 @@ def make_oifits(self): elif self.method == "mean": - self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.closure_phases, self.e_cp, self.t3amp, self.e_t3amp, self.camp, self.e_camp, self.q4phi, self.e_q4phi, self.pist, self.e_pist = self.average_observables(np.mean) + (self.vis2, self.e_vis2, self.visamp, self.e_visamp, + self.visphi, self.e_visphi, self.closure_phases, self.e_cp, + self.t3amp, self.e_t3amp, self.camp, self.e_camp, + self.q4phi, self.e_q4phi, self.pist, self.e_pist) = self.average_observables(np.mean) else: # take the median - self.vis2, self.e_vis2, self.visamp, self.e_visamp, self.visphi, self.e_visphi, self.closure_phases, self.e_cp, self.t3amp, self.e_t3amp, self.camp, self.e_camp, self.q4phi, self.e_q4phi, self.pist, self.e_pist = self.average_observables(np.median) + (self.vis2, self.e_vis2, self.visamp, self.e_visamp, + self.visphi, self.e_visphi, self.closure_phases, self.e_cp, + self.t3amp, self.e_t3amp, self.camp, self.e_camp, + self.q4phi, self.e_q4phi, self.pist, self.e_pist) = self.average_observables(np.median) # Convert angular quantities from radians to degrees self.visphi = np.rad2deg(self.visphi) @@ -327,7 +386,6 @@ def make_oifits(self): self.pist = np.rad2deg(self.pist) self.e_pist = np.rad2deg(self.e_pist) - # prepare arrays for OI_ARRAY ext self.staxy = instrument_data.ctrs_inst tel_name = ["A%i" % x for x in np.arange(self.n_holes) + 1] @@ -343,106 +401,105 @@ def make_oifits(self): sta_index = np.arange(self.n_holes) + 1 pscale = instrument_data.pscale_mas / 1000.0 # arcsec - isz = self.fringe_fitter.scidata.shape[ - 1 - ] # Size of the image to extract NRM data + # Size of the image to extract NRM data + isz = self.fringe_fitter.scidata.shape[1] fov = [pscale * isz] * self.n_holes fovtype = ["RADIUS"] * self.n_holes - m = datamodels.AmiOIModel() - self.init_oimodel_arrays(m) + oim = datamodels.AmiOIModel() + self.init_oimodel_arrays(oim) # primary header keywords - m.meta.telescope = instrument_data.telname - m.meta.origin = "STScI" - m.meta.instrument.name = instrument_data.instrument - m.meta.program.pi_name = instrument_data.pi_name - m.meta.target.proposer_name = instrument_data.proposer_name - m.meta.observation.date = observation_date.fits - m.meta.oifits.array_name = instrument_data.arrname - m.meta.oifits.instrument_mode = instrument_data.pupil - - m.meta.ami.roll_ref = instrument_data.roll_ref - m.meta.ami.v3yangle = instrument_data.v3iyang - m.meta.ami.vparity = instrument_data.vparity + oim.meta.telescope = instrument_data.telname + oim.meta.origin = "STScI" + oim.meta.instrument.name = instrument_data.instrument + oim.meta.program.pi_name = instrument_data.pi_name + oim.meta.target.proposer_name = instrument_data.proposer_name + oim.meta.observation.date = observation_date.fits + oim.meta.oifits.array_name = instrument_data.arrname + oim.meta.oifits.instrument_mode = instrument_data.pupil + + oim.meta.ami.roll_ref = instrument_data.roll_ref + oim.meta.ami.v3yangle = instrument_data.v3iyang + oim.meta.ami.vparity = instrument_data.vparity # oi_array extension data - m.array["TEL_NAME"] = tel_name - m.array["STA_NAME"] = sta_name - m.array["STA_INDEX"] = sta_index - m.array["DIAMETER"] = diameter - m.array["STAXYZ"] = staxyz - m.array["FOV"] = fov - m.array["FOVTYPE"] = fovtype - m.array["CTRS_EQT"] = instrument_data.ctrs_eqt - m.array["PISTONS"] = self.pist - m.array["PIST_ERR"] = self.e_pist + oim.array["TEL_NAME"] = tel_name + oim.array["STA_NAME"] = sta_name + oim.array["STA_INDEX"] = sta_index + oim.array["DIAMETER"] = diameter + oim.array["STAXYZ"] = staxyz + oim.array["FOV"] = fov + oim.array["FOVTYPE"] = fovtype + oim.array["CTRS_EQT"] = instrument_data.ctrs_eqt + oim.array["PISTONS"] = self.pist + oim.array["PIST_ERR"] = self.e_pist # oi_target extension data - m.target['TARGET_ID'] = [1] - m.target['TARGET'] = instrument_data.objname - m.target['RAEP0'] = instrument_data.ra - m.target['DECEP0'] = instrument_data.dec - m.target['EQUINOX'] = [2000] - m.target['RA_ERR'] = instrument_data.ra_uncertainty - m.target['DEC_ERR'] = instrument_data.dec_uncertainty - m.target['SYSVEL'] = [0] - m.target['VELTYP'] = ['UNKNOWN'] - m.target['VELDEF'] = ['OPTICAL'] - m.target['PMRA'] = instrument_data.pmra - m.target['PMDEC'] = instrument_data.pmdec - m.target['PMRA_ERR'] = [0] - m.target['PMDEC_ERR'] = [0] - m.target['PARALLAX'] = [0] - m.target['PARA_ERR'] = [0] - m.target['SPECTYP'] = ['UNKNOWN'] + oim.target['TARGET_ID'] = [1] + oim.target['TARGET'] = instrument_data.objname + oim.target['RAEP0'] = instrument_data.ra + oim.target['DECEP0'] = instrument_data.dec + oim.target['EQUINOX'] = [2000] + oim.target['RA_ERR'] = instrument_data.ra_uncertainty + oim.target['DEC_ERR'] = instrument_data.dec_uncertainty + oim.target['SYSVEL'] = [0] + oim.target['VELTYP'] = ['UNKNOWN'] + oim.target['VELDEF'] = ['OPTICAL'] + oim.target['PMRA'] = instrument_data.pmra + oim.target['PMDEC'] = instrument_data.pmdec + oim.target['PMRA_ERR'] = [0] + oim.target['PMDEC_ERR'] = [0] + oim.target['PARALLAX'] = [0] + oim.target['PARA_ERR'] = [0] + oim.target['SPECTYP'] = ['UNKNOWN'] # oi_vis extension data - m.vis['TARGET_ID'] = 1 - m.vis['TIME'] = 0 - m.vis['MJD'] = observation_date.mjd - m.vis['INT_TIME'] = instrument_data.itime - m.vis['VISAMP'] = self.visamp - m.vis['VISAMPERR'] = self.e_visamp - m.vis['VISPHI'] = self.visphi - m.vis['VISPHIERR'] = self.e_visphi - m.vis['UCOORD'] = ucoord - m.vis['VCOORD'] = vcoord - m.vis['STA_INDEX'] = self._format_STAINDEX_V2(self.bholes) - m.vis['FLAG'] = flagVis + oim.vis['TARGET_ID'] = 1 + oim.vis['TIME'] = 0 + oim.vis['MJD'] = observation_date.mjd + oim.vis['INT_TIME'] = instrument_data.itime + oim.vis['VISAMP'] = self.visamp + oim.vis['VISAMPERR'] = self.e_visamp + oim.vis['VISPHI'] = self.visphi + oim.vis['VISPHIERR'] = self.e_visphi + oim.vis['UCOORD'] = ucoord + oim.vis['VCOORD'] = vcoord + oim.vis['STA_INDEX'] = self._format_STAINDEX_V2(self.bholes) + oim.vis['FLAG'] = flagVis # oi_vis2 extension data - m.vis2['TARGET_ID'] = 1 - m.vis2['TIME'] = 0 - m.vis2['MJD'] = observation_date.mjd - m.vis2['INT_TIME'] = instrument_data.itime - m.vis2['VIS2DATA'] = self.vis2 - m.vis2['VIS2ERR'] = self.e_vis2 - m.vis2['UCOORD'] = ucoord - m.vis2['VCOORD'] = vcoord - m.vis2['STA_INDEX'] = self._format_STAINDEX_V2(self.bholes) - m.vis2['FLAG'] = flagVis + oim.vis2['TARGET_ID'] = 1 + oim.vis2['TIME'] = 0 + oim.vis2['MJD'] = observation_date.mjd + oim.vis2['INT_TIME'] = instrument_data.itime + oim.vis2['VIS2DATA'] = self.vis2 + oim.vis2['VIS2ERR'] = self.e_vis2 + oim.vis2['UCOORD'] = ucoord + oim.vis2['VCOORD'] = vcoord + oim.vis2['STA_INDEX'] = self._format_STAINDEX_V2(self.bholes) + oim.vis2['FLAG'] = flagVis # oi_t3 extension data - m.t3['TARGET_ID'] = 1 - m.t3['TIME'] = 0 - m.t3['MJD'] = observation_date.mjd - m.t3['T3AMP'] = self.t3amp - m.t3['T3AMPERR'] = self.e_t3amp - m.t3['T3PHI'] = self.closure_phases - m.t3['T3PHIERR'] = self.e_cp - m.t3['U1COORD'] = u1coord - m.t3['V1COORD'] = v1coord - m.t3['U2COORD'] = u2coord - m.t3['V2COORD'] = v2coord - m.t3['STA_INDEX'] = self._format_STAINDEX_T3(self.tholes) - m.t3['FLAG'] = flagT3 + oim.t3['TARGET_ID'] = 1 + oim.t3['TIME'] = 0 + oim.t3['MJD'] = observation_date.mjd + oim.t3['T3AMP'] = self.t3amp + oim.t3['T3AMPERR'] = self.e_t3amp + oim.t3['T3PHI'] = self.closure_phases + oim.t3['T3PHIERR'] = self.e_cp + oim.t3['U1COORD'] = u1coord + oim.t3['V1COORD'] = v1coord + oim.t3['U2COORD'] = u2coord + oim.t3['V2COORD'] = v2coord + oim.t3['STA_INDEX'] = self._format_STAINDEX_T3(self.tholes) + oim.t3['FLAG'] = flagT3 # oi_wavelength extension data - m.wavelength["EFF_WAVE"] = wl - m.wavelength["EFF_BAND"] = e_wl + oim.wavelength["EFF_WAVE"] = wl + oim.wavelength["EFF_BAND"] = e_wl - return m + return oim def init_oimodel_arrays(self, oimodel): """ @@ -555,7 +612,7 @@ def _maketriples_all(self): ------- tarray: integer array Triple hole indices (0-indexed), - float array of two uv vectors in all triangles + float array of two uv vectors in all triangles """ tlist = [] uvlist = [] From e86066cd882ccf2fd7cd6129582c21bbb784e176 Mon Sep 17 00:00:00 2001 From: Zach Burnett Date: Mon, 9 Dec 2024 10:19:51 -0500 Subject: [PATCH 22/26] remove `okify_regtests` script (moved to `ci_watson`) (#8957) --- changes/8957.general.rst | 1 + jwst/scripts/tests/test_scripts.py | 1 - pyproject.toml | 3 --- 3 files changed, 1 insertion(+), 4 deletions(-) create mode 100644 changes/8957.general.rst diff --git a/changes/8957.general.rst b/changes/8957.general.rst new file mode 100644 index 0000000000..91a475dc0f --- /dev/null +++ b/changes/8957.general.rst @@ -0,0 +1 @@ +remove ``okify_regtests`` script (move to ``ci_watson``) diff --git a/jwst/scripts/tests/test_scripts.py b/jwst/scripts/tests/test_scripts.py index b9ba4e8d08..eb133b15dc 100644 --- a/jwst/scripts/tests/test_scripts.py +++ b/jwst/scripts/tests/test_scripts.py @@ -9,7 +9,6 @@ 'asn_make_pool', 'collect_pipeline_cfgs', 'create_data', - 'okify_regtests', 'pointing_summary', 'schemadoc', 'set_telescope_pointing', diff --git a/pyproject.toml b/pyproject.toml index 6d61c69b5c..ef138c0999 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,6 @@ collect_pipeline_cfgs = "jwst.scripts.collect_pipeline_cfgs:main" create_data = "jwst.scripts.create_data:main" csvconvert = "jwst.csv_tools.csvconvert:CSVConvertScript" exp_to_source = "jwst.exp_to_source.main:Main" -okify_regtests = "jwst.scripts.okify_regtests:main" pointing_summary = "jwst.scripts.pointing_summary:main" schemadoc = "jwst.scripts.schemadoc:main" set_telescope_pointing = "jwst.scripts.set_telescope_pointing:main" @@ -106,8 +105,6 @@ sdp = [ ] test = [ "ci-watson>=0.5.0", - "colorama>=0.4.1", - "readchar>=3.0", "ruff", "pysiaf>=0.13.0", "pytest>=6.0.0", From 3d0e6d7d3183749bb7e67a631a8fce80af665e95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:50:23 +0000 Subject: [PATCH 23/26] Bump OpenAstronomy/github-actions-workflows from 1.14.0 to 1.15.0 Bumps [OpenAstronomy/github-actions-workflows](https://github.com/openastronomy/github-actions-workflows) from 1.14.0 to 1.15.0. - [Release notes](https://github.com/openastronomy/github-actions-workflows/releases) - [Commits](https://github.com/openastronomy/github-actions-workflows/compare/d68193b68216da64eafaa618f53c59f5d271c56e...9f1f43251dde69da8613ea8e11144f05cdea41d5) --- updated-dependencies: - dependency-name: OpenAstronomy/github-actions-workflows dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/ci_cron.yml | 2 +- .github/workflows/tests_devdeps.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f5591067eb..cb1591fdeb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: build: needs: [ check ] if: always() && (needs.check.result == 'success' || needs.check.result == 'skipped') - uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish.yml@d68193b68216da64eafaa618f53c59f5d271c56e # v1.14.0 + uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 with: upload_to_pypi: ${{ (github.event_name == 'release') && (github.event.action == 'released') }} targets: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 083d21625a..4c46a1f25f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,7 @@ jobs: python-version: '3.12' - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 check: - uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@d68193b68216da64eafaa618f53c59f5d271c56e # v1.14.0 + uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 with: default_python: "3.12" envs: | @@ -57,7 +57,7 @@ jobs: outputs: context: ${{ steps.context.outputs.context }} test: - uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@d68193b68216da64eafaa618f53c59f5d271c56e # v1.14.0 + uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 needs: [ crds_context ] with: setenv: | diff --git a/.github/workflows/ci_cron.yml b/.github/workflows/ci_cron.yml index 3e7ab20618..c0db35ced5 100644 --- a/.github/workflows/ci_cron.yml +++ b/.github/workflows/ci_cron.yml @@ -34,7 +34,7 @@ jobs: context: ${{ steps.context.outputs.context }} test: if: (github.repository == 'spacetelescope/jwst' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'run scheduled tests'))) - uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@d68193b68216da64eafaa618f53c59f5d271c56e # v1.14.0 + uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 needs: [ crds_context ] with: setenv: | diff --git a/.github/workflows/tests_devdeps.yml b/.github/workflows/tests_devdeps.yml index 57715057a6..dda0e41f77 100644 --- a/.github/workflows/tests_devdeps.yml +++ b/.github/workflows/tests_devdeps.yml @@ -43,7 +43,7 @@ jobs: context: ${{ steps.context.outputs.context }} test: if: (github.repository == 'spacetelescope/jwst' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'run devdeps tests'))) - uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@d68193b68216da64eafaa618f53c59f5d271c56e # v1.14.0 + uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 needs: [ crds_context ] with: setenv: | From e38bf849965fbe0d0cec95cd58d5c3254c2497f7 Mon Sep 17 00:00:00 2001 From: Melanie Clarke Date: Wed, 11 Dec 2024 11:37:14 -0500 Subject: [PATCH 24/26] Update MIRI MRS spec3 regtest for new cube defaults --- jwst/regtest/test_miri_mrs_spec3.py | 42 +++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/jwst/regtest/test_miri_mrs_spec3.py b/jwst/regtest/test_miri_mrs_spec3.py index 1b71a4c984..6e27960713 100644 --- a/jwst/regtest/test_miri_mrs_spec3.py +++ b/jwst/regtest/test_miri_mrs_spec3.py @@ -100,10 +100,14 @@ def run_spec3_ifushort_extract1d(rtdata_module): @pytest.mark.parametrize( 'output', [ - 'jw01024-c1000_t002_miri_ch3-mediumlong_x1d.fits', - 'jw01024-c1000_t002_miri_ch4-mediumlong_x1d.fits', - 'jw01024-c1000_t002_miri_ch3-mediumlong_s3d.fits', - 'jw01024-c1000_t002_miri_ch4-mediumlong_s3d.fits', + 'jw01024-c1000_t002_miri_ch3-long_x1d.fits', + 'jw01024-c1000_t002_miri_ch3-medium_x1d.fits', + 'jw01024-c1000_t002_miri_ch4-long_x1d.fits', + 'jw01024-c1000_t002_miri_ch4-medium_x1d.fits', + 'jw01024-c1000_t002_miri_ch3-long_s3d.fits', + 'jw01024-c1000_t002_miri_ch3-medium_s3d.fits', + 'jw01024-c1000_t002_miri_ch4-long_s3d.fits', + 'jw01024-c1000_t002_miri_ch4-medium_s3d.fits', ] ) def test_spec3_ifulong(run_spec3_ifulong, fitsdiff_default_kwargs, output): @@ -122,10 +126,14 @@ def test_spec3_ifulong(run_spec3_ifulong, fitsdiff_default_kwargs, output): @pytest.mark.parametrize( 'output', [ - 'jw01024-c1000_t002_miri_ch1-mediumlong_x1d.fits', - 'jw01024-c1000_t002_miri_ch2-mediumlong_x1d.fits', - 'jw01024-c1000_t002_miri_ch1-mediumlong_s3d.fits', - 'jw01024-c1000_t002_miri_ch2-mediumlong_s3d.fits' + 'jw01024-c1000_t002_miri_ch1-long_x1d.fits', + 'jw01024-c1000_t002_miri_ch1-medium_x1d.fits', + 'jw01024-c1000_t002_miri_ch2-long_x1d.fits', + 'jw01024-c1000_t002_miri_ch2-medium_x1d.fits', + 'jw01024-c1000_t002_miri_ch1-long_s3d.fits', + 'jw01024-c1000_t002_miri_ch1-medium_s3d.fits', + 'jw01024-c1000_t002_miri_ch2-long_s3d.fits', + 'jw01024-c1000_t002_miri_ch2-medium_s3d.fits' ], ) def test_spec3_ifushort(run_spec3_ifushort, fitsdiff_default_kwargs, output): @@ -145,10 +153,14 @@ def test_spec3_ifushort(run_spec3_ifushort, fitsdiff_default_kwargs, output): @pytest.mark.parametrize( 'output', [ - 'miri_mrs_emsm_ch1-mediumlong_x1d.fits', - 'miri_mrs_emsm_ch2-mediumlong_x1d.fits', - 'miri_mrs_emsm_ch1-mediumlong_s3d.fits', - 'miri_mrs_emsm_ch2-mediumlong_s3d.fits' + 'miri_mrs_emsm_ch1-long_x1d.fits', + 'miri_mrs_emsm_ch1-medium_x1d.fits', + 'miri_mrs_emsm_ch2-long_x1d.fits', + 'miri_mrs_emsm_ch2-medium_x1d.fits', + 'miri_mrs_emsm_ch1-long_s3d.fits', + 'miri_mrs_emsm_ch1-medium_s3d.fits', + 'miri_mrs_emsm_ch2-long_s3d.fits', + 'miri_mrs_emsm_ch2-medium_s3d.fits' ], ) def test_spec3_ifushort_emsm(run_spec3_ifushort_emsm, fitsdiff_default_kwargs, output): @@ -168,8 +180,10 @@ def test_spec3_ifushort_emsm(run_spec3_ifushort_emsm, fitsdiff_default_kwargs, o @pytest.mark.parametrize( 'output', [ - 'jw01024-c1000_t002_extract1dtest_miri_ch1-mediumlong_x1d.fits', - 'jw01024-c1000_t002_extract1dtest_miri_ch2-mediumlong_x1d.fits' + 'jw01024-c1000_t002_extract1dtest_miri_ch1-long_x1d.fits', + 'jw01024-c1000_t002_extract1dtest_miri_ch1-medium_x1d.fits', + 'jw01024-c1000_t002_extract1dtest_miri_ch2-long_x1d.fits', + 'jw01024-c1000_t002_extract1dtest_miri_ch2-medium_x1d.fits' ], ) def test_spec3_ifushort_extract1d(run_spec3_ifushort_extract1d, fitsdiff_default_kwargs, output): From 974682f39e52e3d719c8d338b8f3ab32a644c313 Mon Sep 17 00:00:00 2001 From: Zach Burnett Date: Thu, 12 Dec 2024 15:48:29 -0500 Subject: [PATCH 25/26] [CI] set default CRDS context to `jwst-edit` (#8987) --- .github/workflows/ci.yml | 20 +++++--------------- .github/workflows/ci_cron.yml | 18 ++++-------------- .github/workflows/contexts.yml | 26 -------------------------- .github/workflows/tests_devdeps.yml | 18 ++++-------------- 4 files changed, 13 insertions(+), 69 deletions(-) delete mode 100644 .github/workflows/contexts.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c46a1f25f..1d712bc1a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,8 @@ on: type: string required: false default: '' - crds_server: - description: CRDS server + crds_server_url: + description: CRDS server URL type: string required: false default: https://jwst-crds.stsci.edu @@ -46,28 +46,18 @@ jobs: envs: | - linux: check-dependencies - linux: check-types - latest_crds_contexts: - uses: ./.github/workflows/contexts.yml - crds_context: - needs: [ latest_crds_contexts ] - runs-on: ubuntu-latest - steps: - - id: context - run: echo context=${{ github.event_name == 'workflow_dispatch' && (inputs.crds_context != '' && inputs.crds_context || needs.latest_crds_contexts.outputs.jwst) || needs.latest_crds_contexts.outputs.jwst }} >> $GITHUB_OUTPUT - outputs: - context: ${{ steps.context.outputs.context }} test: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 needs: [ crds_context ] with: setenv: | CRDS_PATH: /tmp/data/crds_cache - CRDS_SERVER_URL: ${{ github.event_name == 'workflow_dispatch' && inputs.crds_server || 'https://jwst-crds.stsci.edu' }} - CRDS_CONTEXT: ${{ needs.crds_context.outputs.context }} + CRDS_SERVER_URL: ${{ inputs.crds_server_url || 'https://jwst-crds.stsci.edu' }} + CRDS_CONTEXT: ${{ inputs.crds_context || 'jwst-edit' }} CRDS_CLIENT_RETRY_COUNT: 3 CRDS_CLIENT_RETRY_DELAY_SECONDS: 20 cache-path: /tmp/data/crds_cache - cache-key: crds-${{ needs.crds_context.outputs.context }} + cache-key: crds-${{ inputs.crds_context || 'jwst-edit' }} envs: | - linux: py310-oldestdeps-xdist-cov pytest-results-summary: true diff --git a/.github/workflows/ci_cron.yml b/.github/workflows/ci_cron.yml index c0db35ced5..3f19dbe425 100644 --- a/.github/workflows/ci_cron.yml +++ b/.github/workflows/ci_cron.yml @@ -11,8 +11,8 @@ on: type: string required: false default: '' - crds_server: - description: CRDS server + crds_server_url: + description: CRDS server URL type: string required: false default: https://jwst-crds.stsci.edu @@ -22,16 +22,6 @@ concurrency: cancel-in-progress: true jobs: - latest_crds_contexts: - uses: ./.github/workflows/contexts.yml - crds_context: - needs: [ latest_crds_contexts ] - runs-on: ubuntu-latest - steps: - - id: context - run: echo context=${{ github.event_name == 'workflow_dispatch' && (inputs.crds_context != '' && inputs.crds_context || needs.latest_crds_contexts.outputs.jwst) || needs.latest_crds_contexts.outputs.jwst }} >> $GITHUB_OUTPUT - outputs: - context: ${{ steps.context.outputs.context }} test: if: (github.repository == 'spacetelescope/jwst' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'run scheduled tests'))) uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 @@ -39,8 +29,8 @@ jobs: with: setenv: | CRDS_PATH: /tmp/crds_cache - CRDS_SERVER_URL: ${{ github.event_name == 'workflow_dispatch' && inputs.crds_server || 'https://jwst-crds.stsci.edu' }} - CRDS_CONTEXT: ${{ needs.crds_context.outputs.context }} + CRDS_SERVER_URL: ${{ inputs.crds_server_url || 'https://jwst-crds.stsci.edu' }} + CRDS_CONTEXT: ${{ inputs.crds_context || 'jwst-edit' }} CRDS_CLIENT_RETRY_COUNT: 3 CRDS_CLIENT_RETRY_DELAY_SECONDS: 20 cache-path: /tmp/crds_cache diff --git a/.github/workflows/contexts.yml b/.github/workflows/contexts.yml deleted file mode 100644 index b3047bdb63..0000000000 --- a/.github/workflows/contexts.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: contexts - -on: - workflow_call: - outputs: - jwst: - value: ${{ jobs.contexts.outputs.jwst }} - workflow_dispatch: - -jobs: - contexts: - name: retrieve latest CRDS contexts - runs-on: ubuntu-latest - outputs: - jwst: ${{ steps.jwst_crds_context.outputs.pmap }} - steps: - - id: jwst_crds_context - env: - OBSERVATORY: jwst - CRDS_SERVER_URL: https://jwst-crds.stsci.edu - run: > - echo "pmap=$( - curl -s -X POST -d '{"jsonrpc": "1.0", "method": "get_default_context", "params": ["${{ env.OBSERVATORY }}", null], "id": 1}' ${{ env.CRDS_SERVER_URL }}/json/ --retry 8 --connect-timeout 10 | - python -c "import sys, json; print(json.load(sys.stdin)['result'])" - )" >> $GITHUB_OUTPUT - - run: if [[ ! -z "${{ steps.jwst_crds_context.outputs.pmap }}" ]]; then echo ${{ steps.jwst_crds_context.outputs.pmap }}; else exit 1; fi diff --git a/.github/workflows/tests_devdeps.yml b/.github/workflows/tests_devdeps.yml index dda0e41f77..8db7c09c01 100644 --- a/.github/workflows/tests_devdeps.yml +++ b/.github/workflows/tests_devdeps.yml @@ -20,8 +20,8 @@ on: type: string required: false default: '' - crds_server: - description: CRDS server + crds_server_url: + description: CRDS server URL type: string required: false default: https://jwst-crds.stsci.edu @@ -31,16 +31,6 @@ concurrency: cancel-in-progress: true jobs: - latest_crds_contexts: - uses: ./.github/workflows/contexts.yml - crds_context: - needs: [ latest_crds_contexts ] - runs-on: ubuntu-latest - steps: - - id: context - run: echo context=${{ github.event_name == 'workflow_dispatch' && (inputs.crds_context != '' && inputs.crds_context || needs.latest_crds_contexts.outputs.jwst) || needs.latest_crds_contexts.outputs.jwst }} >> $GITHUB_OUTPUT - outputs: - context: ${{ steps.context.outputs.context }} test: if: (github.repository == 'spacetelescope/jwst' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'run devdeps tests'))) uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 @@ -48,8 +38,8 @@ jobs: with: setenv: | CRDS_PATH: /tmp/data/crds_cache - CRDS_SERVER_URL: ${{ github.event_name == 'workflow_dispatch' && inputs.crds_server || 'https://jwst-crds.stsci.edu' }} - CRDS_CONTEXT: ${{ needs.crds_context.outputs.context }} + CRDS_SERVER_URL: ${{ inputs.crds_server_url || 'https://jwst-crds.stsci.edu' }} + CRDS_CONTEXT: ${{ inputs.crds_context || 'jwst-edit' }} CRDS_CLIENT_RETRY_COUNT: 3 CRDS_CLIENT_RETRY_DELAY_SECONDS: 20 cache-path: /tmp/data/crds_cache From 8b5e7bf858abebe5eaff5af512eb1730d9aa6014 Mon Sep 17 00:00:00 2001 From: zacharyburnett Date: Thu, 12 Dec 2024 15:50:30 -0500 Subject: [PATCH 26/26] hotfix for merge conflict --- .github/workflows/ci.yml | 1 - .github/workflows/ci_cron.yml | 1 - .github/workflows/tests_devdeps.yml | 1 - 3 files changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d712bc1a6..9dceff2eff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,6 @@ jobs: - linux: check-types test: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 - needs: [ crds_context ] with: setenv: | CRDS_PATH: /tmp/data/crds_cache diff --git a/.github/workflows/ci_cron.yml b/.github/workflows/ci_cron.yml index 3f19dbe425..1822318bbf 100644 --- a/.github/workflows/ci_cron.yml +++ b/.github/workflows/ci_cron.yml @@ -25,7 +25,6 @@ jobs: test: if: (github.repository == 'spacetelescope/jwst' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'run scheduled tests'))) uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 - needs: [ crds_context ] with: setenv: | CRDS_PATH: /tmp/crds_cache diff --git a/.github/workflows/tests_devdeps.yml b/.github/workflows/tests_devdeps.yml index 8db7c09c01..5f79a9982d 100644 --- a/.github/workflows/tests_devdeps.yml +++ b/.github/workflows/tests_devdeps.yml @@ -34,7 +34,6 @@ jobs: test: if: (github.repository == 'spacetelescope/jwst' && (github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'run devdeps tests'))) uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@9f1f43251dde69da8613ea8e11144f05cdea41d5 # v1.15.0 - needs: [ crds_context ] with: setenv: | CRDS_PATH: /tmp/data/crds_cache