Source code for rayoptics.codev.cmdproc

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright © 2018 Michael J. Hayford
""" Functions to read a CODE V .seq file and populate a sequential model

.. Created on Tue Jan 16 10:14:12 2018

.. codeauthor: Michael J. Hayford
"""
import logging
import math

from . import tla
from . import reader as cvr

from rayoptics.elem.surface import (DecenterData, Circular, Rectangular,
                                    Elliptical)
from rayoptics.elem import profiles
from rayoptics.oprops import doe
from rayoptics.seq.medium import GlassHandlerBase
from rayoptics.raytr.opticalspec import Field
from rayoptics.util.misc_math import isanumber

from opticalglass import util

from opticalglass import opticalmedium as om
from opticalglass import modelglass as mg

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
_fh = logging.FileHandler('cv_cmd_proc.log', mode='w', delay=True)
_fh.setLevel(logging.DEBUG)
logger.addHandler(_fh)

_tla = tla.MapTLA()

# Support for CODE V private catalog materials.
# Should this be in OpticalModel?
_reading_private_catalog = False
_private_catalog_wvls = None
_private_catalog_glasses = {}

_glass_handler = None
_track_contents = None


[docs]def fictitious_glass_decode(gc): ''' glass code parser, allowing for greater precision of n and v ''' rindex_part = int(gc) magnitude = int(math.floor(math.log10(rindex_part))) + 1 n = 1.0 + rindex_part/10**magnitude v = round(100.0*(gc - int(gc)), 6) return n, v
[docs]def read_lens(filename, **kwargs): """ given a CODE V .seq filename, return an OpticalModel Args: filename (pathlib.Path): a CODE V .seq file path kwargs (dict): keyword args passed to the reader functions Returns: an OpticalModel instance and a info tuple """ import rayoptics.optical.opticalmodel as opticalmodel global _glass_handler, _track_contents global _reading_private_catalog _reading_private_catalog = False _track_contents = util.Counter() opt_model = opticalmodel.OpticalModel(do_init=False) _glass_handler = CVGlassHandler(filename) cmds = cvr.read_seq_file(filename) for i, c in enumerate(cmds): cmd_fct, tla, qlist, dlist = process_command(c) if cmd_fct: eval_str = cmd_fct + '(opt_model, tla, qlist, dlist)' eval(eval_str) else: logger.info('Line %d: Command %s not supported', i+1, c[0]) post_process_input(opt_model, filename, **kwargs) _glass_handler.save_replacements() _track_contents.update(_glass_handler.track_contents) opt_model.update_model() info = _track_contents, _glass_handler.glasses_not_found return opt_model, info
[docs]def process_command(cmd): global _reading_private_catalog CmdFct, IndxQuals, DataType, Quals = range(4) tla = cmd[0][:3].upper() qlist = [] dlist = [] cmd_fct = None cmd_def = _tla.find(tla) if cmd_def: cmd_fct = cmd_def[CmdFct] iquals = cmd_def[IndxQuals] quals = cmd_def[Quals] data_type = cmd_def[DataType] data_found = False for t in cmd[1:]: if not data_found: if t in quals: qlist.append((t,)) elif t[:1] in iquals: qlist.append((t[:1], t[1:])) else: data_found = True if data_found: if data_type == 'String': dlist.append(t) elif data_type == 'Double': dlist.append(eval("float("+t+")")) elif data_type == 'Integer': dlist.append(eval("int("+t+")")) elif data_type == 'Boolean': if t[:1].upper() == 'N': dlist.append(False) else: dlist.append(True) elif tla[:1] == 'S': cmd_fct = 'surface_cmd' qlist.append((tla[:1], tla[1:])) tla = 'S' dlist.append(float(cmd[1])) # radius/curvature dlist.append(float(cmd[2])) # thickness cmd_len = len(cmd) if cmd_len > 3: dlist.append(cmd[3]) # glass if cmd_len > 4: dlist.append(cmd[4]) # rmd elif _reading_private_catalog and isinstance(cmd[0], str): global _private_catalog_wvls, _private_catalog_glasses label = cmd[0] for t in cmd[1:]: dlist.append(eval("float("+t+")")) prv_glass = om.InterpolatedMedium(label, wvls=_private_catalog_wvls, rndx=dlist, cat='CV private catalog') _private_catalog_glasses[label] = prv_glass return cmd_fct, tla, qlist, dlist
[docs]def log_cmd(label, tla, qlist, dlist): logger.debug("%s: %s %s %s", label, tla, str(qlist), str(dlist))
[docs]def post_process_input(opt_model, filename, **kwargs): global _track_contents sm = opt_model['seq_model'] osp = opt_model['optical_spec'] # retrieve image thickness and set defocus sm.z_dir.pop() sm.rndx.pop() gi = sm.gaps.pop() osp['focus'].focus_shift = gi.thi if opt_model.system_spec.title == '' and filename is not None: fname_full = filename.resolve() cat, fname = fname_full.parts[-2:] title = "{:s}: {:s}".format(cat, fname) opt_model.system_spec.title = title conj_type = 'finite' if math.isinf(sm.gaps[0].thi): sm.gaps[0].thi = 1e10 conj_type = 'infinite' _track_contents['conj type'] = conj_type sm.ifcs[0].label = 'Obj' sm.ifcs[0].interact_mode = 'dummy' sm.ifcs[-1].label = 'Img' sm.ifcs[-1].interact_mode = 'dummy' _track_contents['# surfs'] = len(sm.ifcs) _track_contents['# wvls'] = len(osp['wvls'].wavelengths) _track_contents['fov'] = osp['fov'].key _track_contents['# fields'] = len(osp['fov'].fields)
[docs]def wvl_spec_data(optm, tla, qlist, dlist): osp = optm.optical_spec if tla == "WL": osp.spectral_region.wavelengths = dlist osp.spectral_region.calc_colors() elif tla == "WTW": osp.spectral_region.spectral_wts = dlist elif tla == "REF": osp.spectral_region.reference_wvl = dlist[0]-1 elif tla == "CWL": osp.spectral_region.coating_wvl = dlist
[docs]def pupil_spec_data(optm, tla, qlist, dlist): pupil = optm.optical_spec.pupil if tla == "EPD": pupil.key = 'aperture', 'object', 'pupil' elif tla == "NAO": pupil.key = 'aperture', 'object', 'NA' elif tla == "NA": pupil.key = 'aperture', 'image', 'NA' elif tla == "FNO": pupil.key = 'aperture', 'image', 'f/#' pupil.value = dlist[0] logger.debug("pupil_spec_data: %s %f", tla, dlist[0])
[docs]def field_spec_data(optm, tla, qlist, dlist): fov = optm.optical_spec.field_of_view if tla == 'XOB' or tla == 'YOB': fov.key = 'field', 'object', 'height' elif tla == 'XAN' or tla == 'YAN': fov.key = 'field', 'object', 'angle' elif tla == 'XIM' or tla == 'YIM': fov.key = 'field', 'image', 'height' if len(fov.fields) != len(dlist): fov.fields = [Field() for f in range(len(dlist))] if tla[0] == 'V': attr = tla.lower() elif tla[0] == 'X' or tla[0] == 'Y': attr = tla[0].lower() elif tla == 'WTF': attr = 'wt' for i, f in enumerate(fov.fields): f.__setattr__(attr, dlist[i]) log_cmd("field_spec_data", tla, qlist, dlist)
[docs]def spec_data(optm, tla, qlist, dlist): if tla == "LEN": pass elif tla == "RDM": if len(dlist) == 0: optm.radius_mode = True else: optm.radius_mode = dlist[0] elif tla == "TIT": optm.system_spec.title = dlist[0] elif tla == "INI": optm.system_spec.initials = dlist[0] elif tla == "DIM": dim = dlist[0].upper() if dim == 'M' or dim == 'MM': dim = 'mm' elif dim == 'C' or dim == 'CM': dim = 'cm' elif dim == 'I' or dim == 'IN': dim = 'inches' optm.system_spec.dimensions = dim elif tla == "TEM": optm.system_spec.temperature = dlist[0] elif tla == "PRE": optm.system_spec.pressure = dlist[0] log_cmd("spec_data", tla, qlist, dlist)
[docs]def get_index_qualifier(seq_model, qtype, qlist): def num_or_alpha(idx): if idx.isdigit(): return int(idx) elif idx.isalpha(): if idx == 'O': return 0 elif idx == 'I': return seq_model.get_num_surfaces()-1 elif idx == 'S': return seq_model.stop_surface elif idx.isascii(): if qtype == 'L': return idx else: return None for q in qlist: if q[0] == qtype: if q[1].find('..') > 0: i = q[1].find('..') return (num_or_alpha(q[1][:i]), num_or_alpha(q[1][i+2:])) else: return num_or_alpha(q[1]),
[docs]def surface_cmd(opt_model, tla, qlist, dlist): seq_model = opt_model.seq_model idx, = get_index_qualifier(seq_model, 'S', qlist) update_surface_and_gap(opt_model, dlist, idx)
[docs]def update_surface_and_gap(opt_model, dlist, idx=None): global _glass_handler seq_model = opt_model.seq_model s, g = seq_model.insert_surface_and_gap() if opt_model.radius_mode: if dlist[0] != 0.0: s.profile.cv = 1.0/dlist[0] else: s.profile.cv = 0.0 else: s.profile.cv = dlist[0] if g: g.thi = dlist[1] if len(dlist) < 3: g.medium = om.Air() else: if dlist[2].upper() == 'REFL': s.interact_mode = 'reflect' g.medium = seq_model.gaps[seq_model.cur_surface-1].medium else: g.medium = _glass_handler.process_glass_data(dlist[2]) else: # at image surface, apply defocus to previous thickness seq_model.gaps[idx-1].thi += dlist[1]
[docs]def private_catalog(optm, tla, qlist, dlist): global _reading_private_catalog, _private_catalog_wvls if tla == "PRV": _reading_private_catalog = True elif tla == "PWL": _private_catalog_wvls = dlist elif tla == "END": _reading_private_catalog = False _private_catalog_wvls = None log_cmd("private_catalog", tla, qlist, dlist)
[docs]def surface_data(optm, tla, qlist, dlist): seq_model = optm.seq_model idx = get_index_qualifier(seq_model, 'S', qlist) if not idx: idx = seq_model.cur_surface if tla == 'SLB': seq_model.ifcs[idx].label = dlist[0] elif tla == 'STO': seq_model.stop_surface = idx elif tla == 'THI': seq_model.gaps[idx].thi = dlist[0] elif tla == 'SPH': update_surface_profile(seq_model, 'Spherical', idx) elif tla == 'CON': update_surface_profile(seq_model, 'Conic', idx) elif tla == 'ASP': update_surface_profile(seq_model, 'EvenPolynomial', idx) elif tla == 'XTO': update_surface_profile(seq_model, 'XToroid', idx) elif tla == 'YTO': update_surface_profile(seq_model, 'YToroid', idx) log_cmd("surface_data", tla, qlist, dlist)
[docs]def update_surface_profile(seq_model, profile_type, idx=None): if not isinstance(idx, int): idx = seq_model.cur_surface cur_profile = seq_model.ifcs[idx].profile new_profile = profiles.mutate_profile(cur_profile, profile_type) seq_model.ifcs[idx].profile = new_profile return seq_model.ifcs[idx].profile
[docs]def profile_data(optm, tla, qlist, dlist): seq_model = optm.seq_model idx = get_index_qualifier(seq_model, 'S', qlist) if not idx: idx = seq_model.cur_surface if tla == 'CUX': seq_model.ifcs[idx].profile.cv = dlist[0] elif tla == 'CUY': seq_model.ifcs[idx].profile.cv = dlist[0] elif tla == 'RDX': if dlist[0] != 0.0: seq_model.ifcs[idx].profile.cv = 1.0/dlist[0] else: seq_model.ifcs[idx].profile.cv = 0.0 elif tla == 'RDY': if dlist[0] != 0.0: seq_model.ifcs[idx].profile.cv = 1.0/dlist[0] else: seq_model.ifcs[idx].profile.cv = 0.0 elif tla == 'K': seq_model.ifcs[idx].profile.cc = dlist[0] elif tla == 'A': seq_model.ifcs[idx].profile.coef4 = dlist[0] elif tla == 'B': seq_model.ifcs[idx].profile.coef6 = dlist[0] elif tla == 'C': seq_model.ifcs[idx].profile.coef8 = dlist[0] elif tla == 'D': seq_model.ifcs[idx].profile.coef10 = dlist[0] elif tla == 'E': seq_model.ifcs[idx].profile.coef12 = dlist[0] elif tla == 'F': seq_model.ifcs[idx].profile.coef14 = dlist[0] elif tla == 'G': seq_model.ifcs[idx].profile.coef16 = dlist[0] elif tla == 'H': seq_model.ifcs[idx].profile.coef18 = dlist[0] elif tla == 'J': seq_model.ifcs[idx].profile.coef20 = dlist[0] log_cmd("profile_data", tla, qlist, dlist)
[docs]def aperture_data(opm, tla, qlist, dlist): """ add aperture data, either creating a new aperture or modifying the last """ seq_model = opm.seq_model idx = get_index_qualifier(seq_model, 'S', qlist) lbl = get_index_qualifier(seq_model, 'L', qlist) if not idx: idx = seq_model.cur_surface ca_type = tla[0] data_type = tla[2] ifc = seq_model.ifcs[idx] ca_list = ifc.clear_apertures for q in qlist: if q[0] == 'EDG': ca_list = ifc.edge_apertures elif q[0] == 'HOL': log_cmd("aperture_data", tla, qlist, dlist) # ca_list = ifc.holes return elif q[0] == 'OBS': log_cmd("aperture_data", tla, qlist, dlist) return if len(ca_list) == 0 or ca_type != type(ca_list[-1]).__name__[0]: if ca_type == 'C': ca = Circular() elif ca_type == 'R': ca = Rectangular() elif ca_type == 'E': ca = Elliptical() ca_list.append(ca) else: ca = ca_list[-1] if data_type == 'R': ca.radius = dlist[0] elif data_type == 'X': ca.x_half_width = dlist[0] elif data_type == 'Y': ca.y_half_width = dlist[0] log_cmd("aperture_data", tla, qlist, dlist)
[docs]def aperture_data_general(opm, tla, qlist, dlist): """ handle the general aperture commands, add to end of list """ seq_model = opm.seq_model idx = get_index_qualifier(seq_model, 'S', qlist) if not idx: idx = seq_model.cur_surface ca_type = tla[0] if ca_type == 'C': ca = Circular(radius=dlist[0], x_offset=dlist[1], y_offset=dlist[2], rotation=dlist[3]) elif ca_type == 'R': ca = Rectangular(x_half_width=dlist[0], y_half_width=dlist[1], x_offset=dlist[2], y_offset=dlist[3], rotation=dlist[3]) elif ca_type == 'E': ca = Elliptical(x_half_width=dlist[0], y_half_width=dlist[1], x_offset=dlist[2], y_offset=dlist[3], rotation=dlist[3]) seq_model.ifcs[idx].clear_apertures.append(ca) log_cmd("aperture_data_general", tla, qlist, dlist)
[docs]def aperture_offset(opm, tla, qlist, dlist): """ handle the aperture offset commands, assume last aperture in list """ seq_model = opm.seq_model idx = get_index_qualifier(seq_model, 'S', qlist) if not idx: idx = seq_model.cur_surface ca = seq_model.ifcs[idx].clear_apertures[-1] offset_type = tla[2] if offset_type == 'X': ca.x_offset = dlist[0] elif offset_type == 'Y': ca.y_offset = dlist[0] elif offset_type == 'O': ca.rotation = dlist[0] log_cmd("aperture_offset", tla, qlist, dlist)
[docs]def decenter_data(optm, tla, qlist, dlist): seq_model = optm.seq_model idx = get_index_qualifier(seq_model, 'S', qlist) if not idx: idx = seq_model.cur_surface ifc = seq_model.ifcs[idx] if not ifc.decenter: ifc.decenter = DecenterData('decenter') decenter = ifc.decenter if tla == 'XDE': decenter.dec[0] = dlist[0] elif tla == 'YDE': decenter.dec[1] = dlist[0] elif tla == 'ZDE': decenter.dec[2] = dlist[0] elif tla == 'ADE': decenter.euler[0] = dlist[0] elif tla == 'BDE': decenter.euler[1] = dlist[0] elif tla == 'CDE': decenter.euler[2] = dlist[0] elif tla == 'DAR': decenter.dtype = 'dec and return' elif tla == 'BEN': decenter.dtype = 'bend' elif tla == 'REV': decenter.dtype = 'reverse' decenter.update() log_cmd("decenter_data", tla, qlist, dlist)
# DIF DOE # HOR 1.0 # HWL 587.5618; HCT R # HCO C1 -0.001807322521767816; HCC C1 100
[docs]def diffractive_optic(optm, tla, qlist, dlist): seq_model = optm.seq_model idx = get_index_qualifier(seq_model, 'S', qlist) if not idx: idx = seq_model.cur_surface ifc = seq_model.ifcs[idx] if tla == "DIF": for q in qlist: if "DOE" == q[0]: ifc.phase_element = doe.DiffractiveElement() elif tla == "HOR": if hasattr(ifc, 'phase_element'): ifc.phase_element.order = dlist[0] elif tla == "HWL": if hasattr(ifc, 'phase_element'): ifc.phase_element.ref_wl = dlist[0] elif tla == "HCT": if hasattr(ifc, 'phase_element'): for q in qlist: if "R" == q[0]: ifc.phase_element.phase_fct = doe.radial_phase_fct elif tla == "HCO": cidx = get_index_qualifier(seq_model, 'C', qlist)[0] if hasattr(ifc, 'phase_element'): num_coefs = len(ifc.phase_element.coefficients) coefs = ifc.phase_element.coefficients if cidx <= num_coefs: coefs[cidx-1] = dlist[0] elif cidx == num_coefs + 1: coefs.append(dlist[0]) else: coefs.extend([0.]*(cidx - num_coefs)) coefs[cidx-1] = dlist[0] log_cmd("diffractive_optic", tla, qlist, dlist)
[docs]class CVGlassHandler(GlassHandlerBase): """Handle glass restoration during CODEV import. This class relies on GlassHandlerBase to provide most of the functionality needed to find the requested glass or a substitute. """
[docs] def process_glass_data(self, glass_data): if isanumber(glass_data): # process as a 6 digit code, no decimal point medium = self.find_6_digit_code(glass_data) if medium is not None: self.track_contents['6 digit code'] += 1 return medium else: # process as fictitious glass code nd, vd = fictitious_glass_decode(float(glass_data)) medium = mg.ModelGlass(nd, vd, glass_data) self.track_contents['fictitious glass'] += 1 return medium else: # look for glass name and optional catalog name_cat = glass_data.split('_') if len(name_cat) == 2: name, catalog = name_cat elif len(name_cat) == 1: name, catalog = glass_data, None if catalog is not None: if catalog.upper() == 'SCHOTT' and name[:1].upper() == 'N': name = name[:1]+'-'+name[1:] elif catalog.upper() == 'OHARA' and name[:1].upper() == 'S': name = name[:1]+'-'+name[1:] if not name[-2:].isdigit() and name[-1].isdigit(): name = name[:-1]+' '+name[-1] medium = self.find_glass(name, catalog, always=False) if medium: return medium else: # name with no data. default to crown glass global _private_catalog_glasses if name in _private_catalog_glasses: medium = _private_catalog_glasses[name] else: medium = om.ConstantIndex(1.5, 'not '+name) return medium