NISP simulations (collimated beam)

Author: Yannick Copin y.copin@ipnl.in2p3.fr

We apply a naive spectrograph model (i.e. including a grism in a collimated beam) to the NISP instrument.

[1]:
# Technical stuff related to the Jupyter notebook
%load_ext autoreload
%autoreload 2
%matplotlib inline
#import mpld3
#mpld3.enable_notebook()
import warnings
#warnings.filterwarnings("ignore")
[2]:
import numpy as N
from matplotlib import pyplot as P
from spectrogrism import spectrogrism as S
from spectrogrism import nisp
from spectrogrism import distortion as D

Zemax simulations

Load the Zemax simulations:

[3]:
datapath = "../spectrogrism/data/"
simulations = S.Configuration([
    ("name", "Zemax"),
    (1, datapath + "run_190315.dat"),            # 1st-order dispersed simulation
    (0, datapath + "run_011115_conf2_o0.dat"),   # 0th-order dispersed simulation
    (2, datapath + "run_161115_conf2_o2.dat"),   # 2nd-order dispersed simulation
    ('J', datapath + "run_071215_conf6_J.dat"),  # J-band undispersed simulation
])
print(simulations)
----------------------- Configuration 'Zemax' ------------------------
  name                : Zemax
  1                   : ../spectrogrism/data/run_190315.dat
  0                   : ../spectrogrism/data/run_011115_conf2_o0.dat
  2                   : ../spectrogrism/data/run_161115_conf2_o2.dat
  J                   : ../spectrogrism/data/run_071215_conf6_J.dat
[4]:
zmx_pos = nisp.ZemaxPositions(simulations)
print(zmx_pos)
/home/ycopin/Softwares/VirtualEnvs/Python3.6/lib/python3.6/site-packages/spectrogrism-0.9-py3.6.egg/spectrogrism/nisp.py:181: UserWarning: Setting approximately null xindeg to 0
  warnings.warn("Setting approximately null xindeg to 0")
/home/ycopin/Softwares/VirtualEnvs/Python3.6/lib/python3.6/site-packages/spectrogrism-0.9-py3.6.egg/spectrogrism/nisp.py:185: UserWarning: Offsetting Yin by -0.85 deg
  warnings.warn("Offsetting Yin by -0.85 deg")
/home/ycopin/Softwares/VirtualEnvs/Python3.6/lib/python3.6/site-packages/spectrogrism-0.9-py3.6.egg/spectrogrism/nisp.py:189: UserWarning: Discarding wavelengths > 1.81 µm
  warnings.warn("Discarding wavelengths > 1.81 µm")
Simulations 'Zemax': 4 mode(s)
  Order #1: ../spectrogrism/data/run_190315.dat
  Order #0: ../spectrogrism/data/run_011115_conf2_o0.dat
  Order #2: ../spectrogrism/data/run_161115_conf2_o2.dat
  Band   J: ../spectrogrism/data/run_071215_conf6_J.dat
  Wavelengths: 13 steps from 1.20 to 1.80 µm
  Coords: 289 sources

Plot input sources:

[5]:
ax = zmx_pos.plot_input()
_images/NISP-S_7_0.png
[6]:
simcfg = zmx_pos.get_simcfg()     # Simulation configuration
print(simcfg)
------------------ Simulation configuration 'Zemax' ------------------
  name                : Zemax
  wave_npx            : 13
  wave_range          : [1.2e-06, 1.8000000000000001e-06]
  modes               : [1 0 2 'J']
  input_coords        : [[-0.00698132 -0.00698132]
 [-0.00698132 -0.00610865]
 [-0.00698132 -0.00523599]
 ...
 [ 0.00698132  0.00523599]
 [ 0.00698132  0.00610865]
 [ 0.00698132  0.00698132]]

Optical modeling

We use here the standard collimated model: telescope + collimator (CoLA) + [grism|filter] + camera (CaLA) + detector, even though the filter/grism is in a f/20 beam (while the CaLA produces a f/10 beam, hence the quoted magnification of 1/2).

[7]:
# Optical modeling
optcfg = nisp.NISP_R  # Optical configuration (default NISP)
print(optcfg)
------------------- Optical configuration 'NISP-R' -------------------
  name                : NISP-R
  wave_ref            : 1.5e-06
  wave_range          : [1.25e-06, 1.85e-06]
  telescope_flength   : 24.5
  grism_dispersion    : 9.8
  grism_prism_material: FS
  grism_grating_material: FS
  grism_prism_angle   : 0.036267941856442165
  grism_grating_rho   : 13.09
  grism_grating_blaze : 0.04537856055185257
  detector_pxsize     : 1.8e-05
  collimator_flength  : 1.924
  collimator_gdist_x0 : -0.00031
  collimator_gdist_y0 : 0.0546
  collimator_gdist_K1 : 0.447
  grism_prism_tiltx   : -0.10530153153699122
  grism_prism_tilty   : 0.006574073515845309
  grism_prism_tiltz   : 0.002207841503772827
  camera_flength      : 0.9755
  camera_gdist_x0     : 0.0018
  camera_gdist_y0     : 0.2185
  camera_gdist_K1     : -0.0625
  detector_dx         : 0.000656
  detector_dy         : 0.00088
[8]:
spectro = S.Spectrograph(optcfg,
                         telescope=S.Telescope(optcfg))
print(spectro)
Reading 'collimator_gdist_K#i' parameters for i=1...1
Reading 'camera_gdist_K#i' parameters for i=1...1
---------------------------- Spectrograph ----------------------------
Telescope: f=24.500 m
  Null geometric distortion
  Null chromatic distortion
Collimator: f=1.924 m
  Geometric distortion: center=(-0.00031, +0.0546), K-coeffs=[0.447], P-coeffs=[]
  Null chromatic distortion
Grism:
  Prism [FS]: A=2.08°, tilts=-362',+23',+8'
  Grating [FS]: rho=13.1 g/mm, blaze=2.60°
  1st-order null-deviation wavelength: 1.24 µm

Camera: f=0.976 m
  Geometric distortion: center=(+0.0018, +0.2185), K-coeffs=[-0.0625], P-coeffs=[]
  Null chromatic distortion
  Detector: pxsize=18 µm
  Offset=(+0.656, +0.880) mm, angle=0.0°

Spectrograph magnification: 0.507
Central dispersion: 13.63 AA/px at 1.50 µm
[9]:
print(" Spectrograph round-trip test ".center(70, '-'))
for mode in simcfg.get('modes', (1, 0, 2)):
    if not spectro.test(simcfg.get_wavelengths(optcfg), mode=mode, verbose=False):
        warnings.warn("Order #{}: backward modeling does not match.".format(mode))
    else:
        print("{}: OK".format(S.str_mode(mode)))
-------------------- Spectrograph round-trip test --------------------
Order #1: OK
Order #0: OK
Order #2: OK
Band J: OK

Imagery mode

Distortion model

[10]:
xy = zmx_pos.xda.sel(mode='J').mean(axis=0).values  # Mean (complex) positions (averaged over wavelengths) (289,)
grid = D.StructuredGrid(xy.reshape(17, 17))
print(grid)
Structured grid: 17 × 17 = 289 positions, y-x-
[11]:
step, angle, offset, center = grid.estimate_parameters(fig=True)
print(u"Offset: ({0.real:.3f}, {0.imag:.3f}), step: {1:.3f}, angle: {2:.1f}°"
     .format(offset, step, N.rad2deg(angle)))
print(u"Center of distortion: ({0.real:.3f}, {0.imag:.3f})".format(center))
Offset: (0.001, 0.000), step: 0.011, angle: 0.0°
Center of distortion: (-0.001, -0.012)
_images/NISP-S_16_1.png

Optical model

[12]:
# result = spectro.adjust(zmx_pos, simcfg, tol=1e-4, modes=zmx_pos.bands,
#                         optparams=['detector_dy', 'detector_dx',
#                                    'collimator_gdist_K1', 'camera_gdist_K1'])
[13]:
kwargs = dict(s=20, edgecolor='k', linewidths=1)  # Outlined symbols
ax = zmx_pos.plot_output(modes=zmx_pos.bands, **kwargs)

phot_pos = spectro.predict_positions(simcfg)

kwargs = {}                 # Default
for band in zmx_pos.bands:
    # Compute RMS on positions
    rms = zmx_pos.compute_rms(phot_pos, mode=band)
    print("Band {} RMS = {:.4f} mm = {:.2f} px".format(
        band, rms / 1e-3, rms / spectro.detector.pxsize))
    phot_pos.plot(ax=ax, zorder=0,  # Draw below Zemax
                  modes=(band,),
                  label="{} {} (RMS={:.1f} px)".format(
                      phot_pos.name, band, rms / spectro.detector.pxsize),
                  **kwargs)

ax.axis([-100, +100, -100, +100])               # [mm]
ax.set_aspect('equal', adjustable='datalim')
ax.legend(fontsize='small', frameon=True, framealpha=0.5, title='')
ax.figure.set_size_inches(12, 10)
Band J RMS = 0.2317 mm = 12.87 px
_images/NISP-S_19_1.png

Spectroscopy mode

[14]:
spe_pos = spectro.predict_positions(simcfg, orders=zmx_pos.orders)
spe_pos.check_alignment(zmx_pos)  # Would raise IndexError if incompatible
[15]:
subsampling = 3             # Subsample output plot
kwargs = dict(s=20, edgecolor='k', linewidths=1)  # Outlined symbols
ax = zmx_pos.plot_output(modes=zmx_pos.orders, subsampling=subsampling, **kwargs)

kwargs = {}                      # Default
for order in zmx_pos.orders:
    # Compute RMS on spectra positions
    rms = zmx_pos.compute_rms(spe_pos, mode=order)
    print("Order #{} RMS = {:.4f} mm = {:.2f} px".format(order, rms / 1e-3, rms / spectro.detector.pxsize))

    spe_pos.plot(ax=ax, zorder=0,  # Draw below Zemax
                 modes=(order,),
                 subsampling=subsampling,
                 label="{} #{} (RMS={:.1f} px)".format(spe_pos.name, order, rms / spectro.detector.pxsize),
                 **kwargs)

ax.axis([-100, +100, -100, +100])               # [mm]
ax.set_aspect('equal', adjustable='datalim')
ax.legend(fontsize='small', frameon=True, framealpha=0.5, title='')
ax.figure.set_size_inches(12, 10)
Order #1 RMS = 0.1596 mm = 8.87 px
Order #0 RMS = 0.2331 mm = 12.95 px
Order #2 RMS = 0.1664 mm = 9.25 px
_images/NISP-S_22_1.png
[16]:
fig, axs = P.subplots(1, 3)
# Position offset quiver plots
for ax, order in zip(axs.ravel(), zmx_pos.orders):
    zmx_pos.plot_offsets(spe_pos, ax=ax, mode=order)
    ax.set_aspect('equal', adjustable='box')
    ax.set_title("Order #{}".format(order))
fig.set_size_inches(12, 5)
_images/NISP-S_23_0.png

This page was generated from NISP-S.ipynb.