Source code for rayoptics.parax.specsheet
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright © 2019 Michael J. Hayford
""" module to facilitate first order definition of an optical model
.. Created on Thu May 16 19:57:47 2019
.. codeauthor: Michael J. Hayford
"""
import math
from rayoptics.parax import firstorder
from rayoptics.parax.idealimager import IdealImager, ideal_imager_setup
from rayoptics.util import dict2d
from rayoptics.parax import etendue
from rayoptics.parax.etendue import (obj_img_set, fld_ape_set,
create_etendue_dict)
conjugate_types = ['finite', 'infinite']
[docs]def create_specsheet(conjugate_type, **inputs):
if conjugate_type == 'finite':
# setup finite conjugate defaults
fev = create_etendue_dict()
fev['field']['object'] = dict([('height', None)])
fev['aperture']['object'] = dict([('f/#', None),
('NA', None)])
fev['field']['image'] = dict([('height', None)])
fev['aperture']['image'] = dict([('f/#', None),
('NA', None)])
fss = SpecSheet('finite', etendue_values=fev, **inputs)
return fss
elif conjugate_type == 'infinite':
# setup infinite conjugate defaults
imager_inputs = {'s': -math.inf}
imager = IdealImager(None, -math.inf, None, None, None)
iev = create_etendue_dict()
iev['field']['object'] = dict([('angle', None)])
iev['aperture']['object'] = dict([('pupil', None)])
iev['field']['image'] = dict([('height', None)])
iev['aperture']['image'] = dict([('f/#', None),
('NA', None)])
ifss = SpecSheet('infinite', imager=imager,
imager_inputs=imager_inputs,
frozen_imager_inputs=[True, True, True, True, False],
etendue_values=iev, **inputs)
return ifss
else:
print('create_specsheet: conjugate_type not recognized',
conjugate_type)
return None
[docs]def create_specsheets():
specsheets = {}
# setup finite conjugate defaults
specsheets['finite'] = create_specsheet('finite')
# setup infinite conjugate defaults
specsheets['infinite'] = create_specsheet('infinite')
return specsheets
[docs]def create_specsheet_from_model(opt_model):
"""Return a specsheet filled with the current data from opt_model."""
specsheet = opt_model.specsheet
if specsheet is None:
conj_type = 'finite'
if opt_model.seq_model.gaps[0].thi > 10e8:
conj_type = 'infinite'
specsheet = create_specsheet(conj_type)
firstorder.specsheet_from_parax_data(opt_model, specsheet)
opt_model.specsheet = specsheet
return specsheet
[docs]class SpecSheet():
""" First order optical specification for :class:`~.OpticalModel`
Attributes:
conjugate_type: one of `infinite`, `finite`
imager: instance of `IdealImager`
imager_inputs: dict of inputs to `ideal_imager_setup`
frozen_imager_inputs: list of booleans, if True the parameter is frozen
etendue_inputs: field and aperture inputs used to define the etendue
etendue_values: dict2D of aperture/field vs object/image
partitions: 'imager', 'field', and 'aperture'; number of items in each
"""
def __init__(self, conjugate_type,
imager=None, imager_inputs=None, frozen_imager_inputs=None,
etendue_inputs=None, etendue_values=None):
self.conjugate_type = conjugate_type
if imager is None:
imager = IdealImager(None, None, None, None, None)
self.imager = imager
self.imager_inputs = imager_inputs if imager_inputs else {}
self.frozen_imager_inputs = (frozen_imager_inputs
if frozen_imager_inputs
else [False]*5)
self.etendue_inputs = (etendue_inputs if etendue_inputs
else create_etendue_dict())
self.etendue_values = (etendue_values if etendue_values
else create_etendue_dict())
self.partition_defined()
def __json_encode__(self):
attrs = dict(vars(self))
if hasattr(self, 'partitions'):
del attrs['partitions']
return attrs
def __str__(self):
return ("{!s} conjugates:\nimager: {}\n"
"imager inputs: {}\n"
"frozen imager inputs: {}\n"
"etendue inputs:\n"
" field: {}\n"
" aperture: {}\n"
"etendue values:\n"
" field: {}\n"
" aperture:\n"
" object: {}\n"
" image: {}"
.format(self.conjugate_type,
self.imager,
self.imager_inputs,
self.frozen_imager_inputs,
self.etendue_inputs['field'],
self.etendue_inputs['aperture'],
self.etendue_values['field'],
self.etendue_values['aperture']['object'],
self.etendue_values['aperture']['image']))
def __repr__(self):
return ("{!s}({!s}, imager={},"
"imager_inputs={},"
"frozen_imager_inputs={},"
"etendue_inputs={},"
"etendue_values={})".format(type(self).__name__,
repr(self.conjugate_type),
repr(self.imager),
repr(self.imager_inputs),
repr(self.frozen_imager_inputs),
repr(self.etendue_inputs),
repr(self.etendue_values)))
[docs] def sync_to_restore(self, opt_model):
# imager is exported as a list. convert back to an IdealImager
self.imager = IdealImager(*self.imager)
[docs] def imager_defined(self):
"""True if the imager is completely specified. """
if self.conjugate_type == 'finite':
imager_defined = 'm' if self.imager.m is not None else False
else:
imager_defined = 'f' if self.imager.f is not None else False
return imager_defined
[docs] def partition_defined(self):
""" which partition defines the imager or None """
num_imager_inputs = len(self.imager_inputs)
li = dict2d.num_items_by_type(self.etendue_inputs,
fld_ape_set, obj_img_set)
num_field_inputs = li['field']
num_aperture_inputs = li['aperture']
partitions = {'imager': num_imager_inputs,
'field': num_field_inputs,
'aperture': num_aperture_inputs}
self.partitions = partitions
max_partition = max(partitions, key=partitions.get)
max_num_inputs = partitions[max_partition]
return max_partition if max_num_inputs == 2 else None, max_num_inputs
[docs] def generate_from_inputs(self, imgr_inputs, etendue_inputs):
""" compute imager and etendue values given input dicts """
max_partition, max_num_inputs = self.partition_defined()
num_imager_inputs = self.partitions['imager']
num_field_inputs = self.partitions['field']
num_aperture_inputs = self.partitions['aperture']
conj_type = self.conjugate_type
imager_inputs = {}
if max_num_inputs <= 1:
# fill in imager_inputs with any previous calculations for m or f
if conj_type == 'finite':
if num_imager_inputs < 2 and self.imager.m is not None:
imager_inputs['m'] = self.imager.m
else:
if num_imager_inputs < 2 and self.imager.f is not None:
imager_inputs['f'] = self.imager.f
# update imager_inputs with user entries
imager_inputs.update(imgr_inputs)
imager_inputs = {k: v for (k, v) in imager_inputs.items()
if v is not None}
# calculate an ideal imager for imager_inputs
imager = ideal_imager_setup(**imager_inputs)
# fill in remaining None values with previous imager data
imager = IdealImager(*[self.imager[i] if p is None else imager[i]
for i, p in enumerate(imager)])
if conj_type == 'finite':
imager_defined = True if imager.m is not None else False
else:
imager_defined = True if imager.f is not None else False
etendue_values = self.etendue_values
for fa_key, fa_value in etendue_inputs.items():
for oi_key, oi_value in fa_value.items():
if conj_type == 'finite':
conj = 'finite'
elif conj_type == 'afocal':
conj = 'infinite'
elif conj_type == 'infinite':
conj = 'finite' if oi_key == 'image' else conj_type
etendue.fill_in_etendue_data(conj, imager, fa_key,
etendue_inputs[fa_key][oi_key],
etendue_values[fa_key][oi_key])
if imager_defined:
if num_field_inputs >= 1 and num_aperture_inputs >= 1:
# we have enough data to calculate all of the etendue grid
ii = etendue.do_etendue_via_imager(conj_type, imager,
etendue_inputs,
etendue_values)
if ii:
imager_inputs[ii[0]] = ii[1]
imager = ideal_imager_setup(**imager_inputs)
etendue.do_etendue_via_imager(conj_type, imager,
etendue_inputs,
etendue_values)
elif num_field_inputs == 1:
# we have enough data to calculate all of the etendue grid
row = dict2d.row(etendue_inputs, 'field')
obj_img_key = 'object' if len(row['object']) else 'image'
etendue.do_field_via_imager(conj_type, imager, etendue_inputs,
obj_img_key, etendue_values)
elif num_aperture_inputs == 1:
# we have enough data to calculate all of the etendue grid
row = dict2d.row(etendue_inputs, 'aperture')
obj_img_key = 'object' if len(row['object']) else 'image'
etendue.do_aperture_via_imager(conj_type, imager,
etendue_inputs, obj_img_key,
etendue_values)
else: # imager not specified
if num_field_inputs == 2 or num_aperture_inputs == 2:
fld_ape_key = 'field' if num_field_inputs == 2 else 'aperture'
# solve for imager
ii = etendue.do_etendue_to_imager(fld_ape_key, etendue_inputs,
etendue_values)
imager_inputs[ii[0]] = ii[1]
imager = ideal_imager_setup(**imager_inputs)
# update etendue grid
etendue.do_etendue_via_imager(conj_type, imager,
etendue_inputs, etendue_values)
self.imager = imager
self.etendue_values = etendue_values
return imager, etendue_values
[docs] def get_etendue_inputs(self, ape_fld_key):
"""returns key, value pair for 'aperture'|'field' ape_fld key. """
for k, v in self.etendue_inputs[ape_fld_key].items():
if len(v) > 0:
obj_img_key = k
for k1, v1 in v.items():
value_key = k1
break
key = ape_fld_key, obj_img_key, value_key
value = self.etendue_inputs[ape_fld_key][obj_img_key][value_key]
return key, value
[docs] def get_parax_start_data(self, thi_0, n_0, n_k):
conj_type = self.conjugate_type
imager = self.imager
yu = [0., 1.]
yu_bar = [1., 0.]
if conj_type == 'infinite':
key, value = self.get_etendue_inputs('aperture')
ape_fld_key, obj_img_key, value_key = key
if obj_img_key == 'object':
if value_key == 'pupil':
slp0 = 0.5*value/thi_0
elif obj_img_key == 'image':
if value_key == 'f/#':
slpk = -1./(2.0*value)
slp0 = slpk*imager.f
elif value_key == 'NA':
slpk = n_k*math.tan(math.asin(value/n_k))
slp0 = slpk*imager.f
yu = [0., slp0]
key, value = self.get_etendue_inputs('field')
ape_fld_key, obj_img_key, value_key = key
if obj_img_key == 'object':
if value_key == 'angle':
ang = math.radians(value)
slpbar0 = math.tan(ang)
ybar0 = -slpbar0*thi_0
elif obj_img_key == 'image':
if value_key == 'height':
ybar0 = value/imager.m
slpbar0 = ybar0/thi_0
yu_bar = [ybar0, slpbar0]
elif conj_type == 'finite':
key, value = self.get_etendue_inputs('aperture')
ape_fld_key, obj_img_key, value_key = key
if obj_img_key == 'object':
if value_key == 'pupil':
slp0 = 0.5*value/thi_0
elif value_key == 'f/#':
slp0 = -1./(2.0*value)
elif value_key == 'NA':
slp0 = n_0*math.tan(math.asin(value/n_0))
elif obj_img_key == 'image':
if value_key == 'f/#':
slpk = -1./(2.0*value)
slp0 = slpk*imager.m
elif value_key == 'NA':
slpk = n_k*math.tan(math.asin(value/n_k))
slp0 = slpk*imager.m
yu = [0., slp0]
key, value = self.get_etendue_inputs('field')
ape_fld_key, obj_img_key, value_key = key
if obj_img_key == 'object':
if value_key == 'angle':
ang = math.radians(value)
slpbar0 = math.tan(ang)
ybar0 = -slpbar0*thi_0
elif value_key == 'height':
ybar0 = -value
slpbar0 = ybar0/thi_0
elif obj_img_key == 'image':
if value_key == 'height':
ybar0 = -value/imager.m
slpbar0 = ybar0/thi_0
yu_bar = [ybar0, slpbar0]
return yu, yu_bar