"""tweak_optics module
This module is used to build new telescope with a modified geometry from the default telescope read in the yaml file.
"""
import batoid
from scipy.spatial.transform import Rotation as transform_rotation
import numpy as np
from ghosts.tools import get_vector
from ghosts.geom import get_optics_rotation, get_optics_translation
from ghosts import reflectivity
[docs]def get_list_of_optics(telescope, debug=False):
""" Get a simple list of optical elements from a `batoid` telescope
.. todo::
`get_list_of_optics` needs to better handle optics which are not of interest, e.g. fake baffle
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
debug : `bool`
print debug information or not
Returns
-------
optics : `list` of `string` objects
a simple list of optical element names
"""
optics = []
for one in telescope.items:
if isinstance(one, batoid.optic.Baffle):
if debug:
print('tweak_optics::get_list_of_optics - Ignoring fake baffle')
else:
pass
else:
[optics.append(two.name) for two in telescope[one.name].items]
return optics
[docs]def make_optics_reflective(telescope, coating='simple', r_frac=[0.02, 0.02, 0.15], debug=False):
""" Applies a simple coating as a unique refraction index for each optical element surface
.. todo::
`make_optics_reflective` should implement wavelength dependent coating for each optical surface
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
coating : `string`
use "simple" for simple coating, just one reflection fraction for all surfaces,
or "smart" to set one coefficient per element type
r_frac : `list` of `float`
the fraction of light that you wish surfaces to reflect, usually of the order of 0.02
use a list of 3 elements, for simple coating only the first one is used
for smart coating all 3 are used for lens, filter and detector
debug : `bool`
print debug information or not
Returns
-------
"""
if coating == 'simple':
reflectivity.make_simple_coating(telescope, r_frac, debug=debug)
elif coating == 'smart':
reflectivity.make_smart_coating(telescope, r_frac, debug=debug)
return 0
[docs]def get_optics_position(telescope, name, axis_i):
""" Internal interface to get the position of an optical element given its name
.. todo::
`get_optics_position` should be a hidden internal interface
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
name : `string`
the name of an optical element
axis_i : `int`
the axis index in [0, 1, 2]
Returns
-------
position_i : `float`
the position along the axis index i
"""
position_i = telescope[name].coordSys.origin[axis_i]
return position_i
[docs]def get_optics_position_x(telescope, name):
""" Proxy to get the position of an optical element along the x-axis
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
name : `string`
the name of an optical element
Returns
-------
pos_x : `float`
the position along the axis x
"""
pos_x = get_optics_position(telescope, name, 0)
return pos_x
[docs]def get_optics_position_y(telescope, name):
""" Proxy to get the position of an optical element along the y-axis
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
name : `string`
the name of an optical element
Returns
-------
pos_y : `float`
the position along the axis y
"""
pos_y = get_optics_position(telescope, name, 1)
return pos_y
[docs]def get_optics_position_z(telescope, name):
""" Proxy to get the position of an optical element along the z axis
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
name : `string`
the name of an optical element
Returns
-------
pos_z : `float`
the position along the axis z
"""
pos_z = get_optics_position(telescope, name, 2)
return pos_z
[docs]def rotate_optic(telescope, name, axis='y', angle=1, debug=False):
""" Rotate one optical element of a telescope around a given axis and for a given rotation angle
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
name : `string`
the name of an optical element
axis : `string`
the name of the rotation axis, usually y
angle : `float`
the value of the rotation angle in degrees
debug : `bool`
print debug information if True
Returns
-------
rotated_telescope : `batoid.telescope`
a new telescope with a rotated optical element
"""
# Rotating
rot = transform_rotation.from_euler(axis, angle, degrees=True)
if debug:
print('Rotation around Y as Euler:\n', rot.as_euler('zyx', degrees=True))
print('Rotation around Y as matrix:\n', rot.as_matrix())
# Rotating one item of the telescope
rotated_telescope = telescope.withLocallyRotatedOptic(name=name, rot=rot.as_matrix())
if debug:
print(f'{name} before rotation:\n', telescope[name].coordSys.rot)
print(f'{name} after rotation:\n', rotated_telescope[name].coordSys.rot)
return rotated_telescope
[docs]def translate_optic(telescope, name, axis='x', distance=0.01):
""" Translate one optical element of a telescope along a given axis and for a given length
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
name : `string`
the name of an optical element
axis : `string`
the name of the rotation axis, usually y
distance : `float`
the value of the shift in meters
Returns
-------
rotated_telescope : `batoid.telescope`
a new telescope with a rotated optical element
"""
vector = get_vector(axis, distance)
translated_telescope = telescope.withLocallyShiftedOptic(name=name, shift=vector)
return translated_telescope
# function to rotate one element of a telescope
[docs]def rotate_optic_vector(telescope, name, angles, verbose=False):
""" Rotate one optical element of a telescope given a list of Euler angles
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
name : `string`
the name of an optical element
angles : `list` of `floats`
the values of Euler angles in degrees as a list,e.g. `[0.1, 0.1, 0.1]`
verbose : `bool`
the verbose mode, true or false
Returns
-------
rotated_telescope : `batoid.telescope`
a new telescope with a rotated optical element
"""
# Rotating around the 3 axis
rot_x = transform_rotation.from_euler('x', angles[0], degrees=True)
rot_xy = rot_x * transform_rotation.from_euler('y', angles[1], degrees=True)
rot_xyz = rot_xy * transform_rotation.from_euler('z', angles[2], degrees=True)
if verbose:
print('Rotation around Y as Euler:\n', rot_xyz.as_euler('zyx', degrees=True))
print('Rotation around Y as matrix:\n', rot_xyz.as_matrix())
# Rotating one item of the telescope
rotated_telescope = telescope.withLocallyRotatedOptic(name=name, rot=rot_xyz.as_matrix())
if verbose:
print(f'{name} before rotation:\n', telescope[name].coordSys.rot)
print(f'{name} after rotation:\n', rotated_telescope[name].coordSys.rot)
return rotated_telescope
# function to translate one element of a telescope
[docs]def translate_optic_vector(telescope, name, shifts):
""" Translate an optical element of a telescope given a list of shifts along x, y and z axis
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
name : `string`
the name of an optical element
shifts : `list` of `floats`
the values of shifts in meters as a list for x, y and z axis, e.g. `[0.001, 0.001, 0.001]`
Returns
-------
rotated_telescope : `batoid.telescope`
a new telescope with a rotated optical element
"""
translated_telescope = telescope.withLocallyShiftedOptic(name=name, shift=shifts)
return translated_telescope
[docs]def randomized_telescope(telescope, max_angle=0.1, max_shift=0.001, verbose=False):
""" Randomly translates and rotates all optical elements of a telescope
according to uniform distributions drown from the given a maximum rotation angle
and shift.
Rotation angles are drawn from a uniform distribution in [-max_angle; +max_angle]
Translation values are drawn from a uniform distribution in [-max_shift; +max_shift]
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
max_angle : `float`
the maximum value of the rotation angle in degree
max_shift : `floats`
the maximum value of the shift in meters
verbose : `bool`
the verbose mode, true or false
Returns
-------
rnd_telescope : `batoid.telescope`
a new telescope with randomized optics position and rotation
"""
# randomly rotate all optical elements
rnd_telescope = telescope
optics_names = get_list_of_optics(rnd_telescope)
# rotations
for optic in optics_names:
rnd_euler_angles = max_angle * 2 * (np.random.random([3]) - 0.5)
rnd_telescope = rotate_optic_vector(rnd_telescope, name=optic, angles=rnd_euler_angles, verbose=verbose)
# translations
for optic in optics_names:
rnd_shifts = max_shift * 2 * (np.random.random([3]) - 0.5)
rnd_telescope = translate_optic_vector(rnd_telescope, name=optic, shifts=rnd_shifts)
return rnd_telescope
[docs]def tweak_telescope(telescope, geom_config):
""" Tweak a telescope using rotations and shifts from a dictionary
.. todo::
`tweak_telescope` should preserve coating
Parameters
----------
telescope : `batoid.telescope`
the optical setup as defined in `batoid`
geom_config : `dict`
a dictionary with shifts and rotations for each optical element
Returns
-------
tweaked_telescope : `batoid.telescope`
a new telescope with tweaked optical elements
"""
tweaked_telescope = telescope
for optics in get_list_of_optics(telescope):
v_translation = get_optics_translation(optics, geom_config)
if v_translation != [0., 0., 0.]:
tweaked_telescope = translate_optic_vector(tweaked_telescope, optics, v_translation)
v_rotation = get_optics_rotation(optics, geom_config)
if v_rotation != [0., 0., 0.]:
tweaked_telescope = rotate_optic_vector(tweaked_telescope, optics, v_rotation)
return tweaked_telescope
[docs]def build_telescope(yaml_geometry="../data/LSST_CCOB_r.yaml"):
""" Build a telescope from a geometry description
Parameters
----------
yaml_geometry : `string`
a yaml telescope geometry
Returns
-------
telescope : `batoid.telescope`
a telescope with the given geometry
"""
# CCOB like geometry, i.e. lenses + filters
telescope = batoid.Optic.fromYaml(yaml_geometry)
# Make refractive interfaces partially reflective
make_optics_reflective(telescope)
return telescope
[docs]def build_telescope_at_600nm(yaml_geometry):
""" Build the reference telescope from a yaml geometry.
Parameters
----------
yaml_geometry : `string`
path to the yaml file with the reference geometry to be used.
Returns
-------
ref_telescope : `batoid.telescope`
the reference optical setup as defined in `batoid` with reflectivity at 600 nm
"""
# A few numbers, specific to 600 nm
ccd_reflectivity_600nm = 0.141338
lens_reflectivity_600nm = 0.004 # 0.4% code by Julien Bolmont
filter_reflectivity_600nm = 0.038 # r band filter documentation stated transmission is 96.2%
# CCOB like geometry, i.e. lenses but no filter
ref_telescope = batoid.Optic.fromYaml(yaml_geometry)
# Make refractive interfaces partially reflective
# Call on current telescope, smart coating is [lens, filter, camera]
make_optics_reflective(ref_telescope, coating='smart',
r_frac=[lens_reflectivity_600nm, filter_reflectivity_600nm,
ccd_reflectivity_600nm])
return ref_telescope
[docs]def build_telescope_from_geom(geom_config):
""" Build the default telescope and tweak its optics given shifts and rotations
from a geometry configuration
Parameters
----------
geom_config : `dict`
a dictionary with shifts and rotations for each optical element
Returns
-------
tw_telescope : `batoid.telescope`
a telescope with the given geometry tweaks
"""
# Build the default telescope
telescope = build_telescope()
# tweak its optics
tw_telescope = tweak_telescope(telescope, geom_config)
# Make refractive interfaces partially reflective
make_optics_reflective(tw_telescope)
return tw_telescope
if __name__ == '__main__':
# test list of optics
ccob_telescope = batoid.Optic.fromYaml("../data/LSST_CCOB_r.yaml")
assert get_list_of_optics(ccob_telescope) == ['L1', 'L2', 'Filter', 'L3', 'Detector'], 'Not a CCOB optical setup'