Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

JP-3739: Update NRM geometry, affine distortion use #8974

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4b372a9
Calculate t3 amplitudes (untested)
rcooper295 Aug 15, 2024
0564e7b
noted where t3amps mistakenly populated with closure amps (quad amps)
rcooper295 Aug 15, 2024
ba1c74a
Preliminary quad phase calculation
rcooper295 Aug 15, 2024
e5beb97
docstring correction
rcooper295 Aug 15, 2024
9b0b13d
Updates to use covariances for error estimates and calculate observab…
rcooper295 Sep 25, 2024
d5003d2
Debugging fixes, now functioning
rcooper295 Sep 26, 2024
db53aec
convert quantities to degrees and fix squared visibility uncertainties
rcooper295 Sep 26, 2024
361bf94
Updated to use mean by default and calculate standard error of the me…
rcooper295 Oct 1, 2024
34ef58e
Fixed input args bandpass and affine2d to expect ASDF files, updated …
rcooper295 Oct 14, 2024
62f205b
point pyproject requirements to stdatamodels branch adding pa, calib …
rcooper295 Nov 18, 2024
a0bd5d0
Carry wcsinfo from input through to ami-oi output, correctly populate…
rcooper295 Nov 19, 2024
bd6144b
update affine log message
rcooper295 Nov 20, 2024
15e79af
f2f 0.8 m, print rotation search to log
rcooper295 Nov 5, 2024
9e9d018
option to use affine from commissioning by passing affine2d='commissi…
rcooper295 Nov 11, 2024
c63e70f
Mask definitions read in from NRM reference file in place of hardcode…
rcooper295 Nov 18, 2024
e2f6981
use updated mask_definition_ami
rcooper295 Nov 18, 2024
7b36466
Keep holeshape for model, converted spaces to tabs
rcooper295 Nov 18, 2024
b44da8d
Clean up, add log messages about affine
rcooper295 Nov 20, 2024
79b8edc
Rename lg_model.NrmModel to lg_model.LgModel to avoid confusion with …
rcooper295 Nov 20, 2024
564b4b5
default affine2d updated to 'commissioning' (special case)
rcooper295 Nov 20, 2024
8f38d3f
stdatamodels requirement back to main branch
rcooper295 Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 65 additions & 3 deletions docs/jwst/ami_analyze/description.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <https://asdf-standard.readthedocs.io/>`_
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
------
Expand Down
18 changes: 14 additions & 4 deletions jwst/ami/ami_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,17 @@
# Throughput (combined filter and source spectrum) calculated here
bandpass = utils.handle_bandpass(bandpass, throughput_model)

rotsearch_d = np.append(


if affine2d is None:
rotsearch_d = np.append(

Check warning on line 132 in jwst/ami/ami_analyze.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze.py#L132

Added line #L132 was not covered by tests
np.arange(
rotsearch_parameters[0], rotsearch_parameters[1], rotsearch_parameters[2]
),
rotsearch_parameters[1],
)
)

log.info(f"Initial values to use for rotation search: {rotsearch_d}")
if affine2d is None:
log.info(f"Initial values to use for rotation search: {rotsearch_d}")

Check warning on line 139 in jwst/ami/ami_analyze.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze.py#L139

Added line #L139 was not covered by tests
# affine2d object, can be overridden by user input affine.
# do rotation search on uncropped median image (assuming rotation constant over exposure)
# replace remaining NaNs in median image with median of surrounding 8 (non-NaN) pixels
Expand Down Expand Up @@ -173,6 +175,14 @@
oversample,
holeshape,
)
log.info(f'Found rotation: {affine2d.rotradccw:.4f} rad ({np.rad2deg(affine2d.rotradccw):.4f} deg)')

Check warning on line 178 in jwst/ami/ami_analyze.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze.py#L178

Added line #L178 was not covered by tests
# the affine2d returned here has only rotation...
# to use rotation and scaling/shear, do some matrix multiplication here??

log.info('Using affine transform with parameters:')
log.info(f'\tmx={affine2d.mx}\tmy={affine2d.my}')
log.info(f'\tsx={affine2d.sx}\tsy={affine2d.sy}')
log.info(f'\txo={affine2d.xo}\tyo={affine2d.yo}')

niriss = instrument_data.NIRISS(filt,
nrm_model,
Expand Down
86 changes: 84 additions & 2 deletions jwst/ami/ami_analyze_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand All @@ -16,11 +21,11 @@
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='commissioning') # ASDF file containing user-defined affine parameters OR 'commssioning'
run_bpfix = boolean(default=True) # Run Fourier bad pixel fix on cropped data
"""

Expand All @@ -32,6 +37,66 @@
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'])

Check warning on line 52 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L50-L52

Added lines #L50 - L52 were not covered by tests

# 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}')

Check warning on line 60 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L55-L60

Added lines #L55 - L60 were not covered by tests

# update attribute and return
self.bandpass = bandpass
return bandpass

Check warning on line 64 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L63-L64

Added lines #L63 - L64 were not covered by tests

except:
message = (f'Could not read bandpass from {self.bandpass}. \

Check warning on line 67 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L66-L67

Added lines #L66 - L67 were not covered by tests
See documentation for info on creating a custom bandpass ASDF file.')
raise Exception(message)

Check warning on line 69 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L69

Added line #L69 was not covered by tests

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(

Check warning on line 78 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L76-L78

Added lines #L76 - L78 were not covered by tests
mx = af['mx'],
my = af['my'],
sx = af['sx'],
sy = af['sy'],
xo = af['xo'],
yo = af['yo'],
rotradccw = af['rotradccw']
)
self.log.info(f'Using affine transform from ASDF file {self.affine2d}')

Check warning on line 87 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L87

Added line #L87 was not covered by tests
# 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}. \

Check warning on line 92 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L89-L92

Added lines #L89 - L92 were not covered by tests
See documentation for info on creating a custom affine2d ASDF file.')
self.log.info('**** DEFAULTING TO USE IDENTITY TRANSFORM ****')
affine2d = None

Check warning on line 95 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L94-L95

Added lines #L94 - L95 were not covered by tests

self.affine2d = affine2d
return affine2d

Check warning on line 98 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L97-L98

Added lines #L97 - L98 were not covered by tests

def process(self, input):
"""
Performs analysis of an AMI mode exposure by applying the LG algorithm.
Expand Down Expand Up @@ -83,6 +148,23 @@
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()

Check warning on line 153 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L153

Added line #L153 was not covered by tests
if affine2d is not None:
if affine2d == 'commissioning':
affine2d = utils.Affine2d(mx=9.92820e-01,
my=9.98540e-01,
sx=6.18605e-03,
sy=-7.27008e-03,
xo=0,
yo=0,
name='commissioning')
self.log.info("Using affine parameters from commissioning.")
else:
affine2d = self.override_affine2d()

Check warning on line 165 in jwst/ami/ami_analyze_step.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/ami_analyze_step.py#L165

Added line #L165 was not covered by tests
# and if it is None, rotation search done in apply_LG_plus

# 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}')
Expand Down
2 changes: 1 addition & 1 deletion jwst/ami/find_affine2d_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
crosscorr_rots = []

for (rot, aff) in zip(rotdegs, affine2d_list):
jw = lg_model.NrmModel(mask='jwst_g7s6c', holeshape=holeshape, over=over, affine2d=aff)
jw = lg_model.LgModel(mask='jwst_ami', holeshape=holeshape, over=over, affine2d=aff)

Check warning on line 101 in jwst/ami/find_affine2d_parameters.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/find_affine2d_parameters.py#L101

Added line #L101 was not covered by tests

jw.set_pixelscale(pixel)
# psf_offset in data coords & pixels. Does it get rotated? Second order errors poss.
Expand Down
23 changes: 16 additions & 7 deletions jwst/ami/instrument_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import logging
import numpy as np

from .mask_definitions import NRM_mask_definitions
from .mask_definition_ami import NRM_definition
from . import utils
from . import bp_fix
from stdatamodels.jwst.datamodels import dqflags
Expand Down Expand Up @@ -91,12 +91,12 @@
# only one NRM on JWST:
self.telname = "JWST"
self.instrument = "NIRISS"
self.arrname = "jwst_g7s6c"
self.holeshape = "hex"
self.mask = NRM_mask_definitions(
self.arrname = "jwst_ami"
self.holeshape = 'hex'
self.mask = NRM_definition(
nrm_model,
maskname=self.arrname,
chooseholes=self.chooseholes,
holeshape=self.holeshape,
chooseholes=self.chooseholes
)

# save affine deformation of pupil object or create a no-deformation object.
Expand Down Expand Up @@ -179,9 +179,18 @@
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

Check warning on line 186 in jwst/ami/instrument_data.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/instrument_data.py#L186

Added line #L186 was not covered by tests

self.vparity = input_model.meta.wcsinfo.vparity
self.v3iyang = input_model.meta.wcsinfo.v3yangle

self.wcsinfo = input_model.meta.wcsinfo

Check warning on line 191 in jwst/ami/instrument_data.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/instrument_data.py#L191

Added line #L191 was not covered by tests


self.parangh = input_model.meta.wcsinfo.roll_ref
self.crpix1 = input_model.meta.wcsinfo.crpix1
self.crpix2 = input_model.meta.wcsinfo.crpix2
Expand Down
72 changes: 72 additions & 0 deletions jwst/ami/leastsqnrm.py
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,43 @@

return arr

def t3_amplitudes(amps, n=7):
"""
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)

Check warning on line 822 in jwst/ami/leastsqnrm.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/leastsqnrm.py#L822

Added line #L822 was not covered by tests

cpamps = np.zeros(int(comb(n, 3)))

Check warning on line 824 in jwst/ami/leastsqnrm.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/leastsqnrm.py#L824

Added line #L824 was not covered by tests

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] = (

Check warning on line 830 in jwst/ami/leastsqnrm.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/leastsqnrm.py#L826-L830

Added lines #L826 - L830 were not covered by tests
arr[kk, ii + kk + 1]
* arr[ii + kk + 1, jj + ii + kk + 2]
* arr[jj + ii + kk + 2, kk]
)

nn += jj + 1

Check warning on line 836 in jwst/ami/leastsqnrm.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/leastsqnrm.py#L836

Added line #L836 was not covered by tests

return cpamps

Check warning on line 838 in jwst/ami/leastsqnrm.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/leastsqnrm.py#L838

Added line #L838 was not covered by tests


def redundant_cps(deltaps, n=7):
"""
Expand Down Expand Up @@ -934,3 +971,38 @@
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
quad phases
"""
arr = populate_antisymmphasearray(deltaps, n=n) # fringe phase array
nn = 0
quad_phases = np.zeros(int(comb(n, 4)))

Check warning on line 994 in jwst/ami/leastsqnrm.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/leastsqnrm.py#L992-L994

Added lines #L992 - L994 were not covered by tests

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] = (

Check warning on line 1000 in jwst/ami/leastsqnrm.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/leastsqnrm.py#L996-L1000

Added lines #L996 - L1000 were not covered by tests
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

Check warning on line 1006 in jwst/ami/leastsqnrm.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/leastsqnrm.py#L1006

Added line #L1006 was not covered by tests

return quad_phases

Check warning on line 1008 in jwst/ami/leastsqnrm.py

View check run for this annotation

Codecov / codecov/patch

jwst/ami/leastsqnrm.py#L1008

Added line #L1008 was not covered by tests
Loading
Loading