Source code for rayoptics.optical.obench

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Import files from `OpticalBenchHub` web page

    This module implements lens import from the `OpticalBenchHub <https://www.photonstophotos.net/GeneralTopics/Lenses/OpticalBench/OpticalBenchHub.htm>`_
    portion of Bill Claff's `PhotonsToPhotos <https://www.photonstophotos.net/>`_
    website.

    To import a file from the website, navigate to the lens you wish to import 
    and select the entire web address of the page. Paste this into the url
    argument of the :func:`~.read_obench_url` function.

.. Created on Sat Jul 24 21:34:49 2021

.. codeauthor: Michael J. Hayford
"""
import requests

from rayoptics.optical.opticalmodel import OpticalModel
from rayoptics.elem.profiles import (EvenPolynomial, RadialPolynomial)
from rayoptics.oprops import doe
from rayoptics.oprops.doe import DiffractiveElement
from rayoptics.raytr.opticalspec import WvlSpec
from rayoptics.util.misc_math import isanumber, is_kinda_big, is_fuzzy_zero

from opticalglass import util
import opticalglass.modelglass as mg

_track_contents = None

[docs] def read_obench_url(url, **kwargs) -> tuple["OpticalModel", tuple[dict, dict]]: ''' given a url to a OpticalBench file, return an OpticalModel and info. ''' global _track_contents _track_contents = util.Counter() obench_input, obench_dict = read_url(url, **kwargs) opt_model = read_lens(obench_dict, **kwargs) if kwargs.get('do_update', True): opt_model.update_model() _track_contents['obench input'] = obench_input # type: ignore return opt_model, (_track_contents, {}) # type: ignore
[docs] def read_url(url, **kwargs) -> tuple[list, dict]: ''' given a url to a OpticalBench file, return an OpticalModel and info. ''' global _track_contents _track_contents = (util.Counter() if _track_contents is None else _track_contents) url1 = url.replace('OpticalBench.htm#', '') url2 = url1.partition(',')[0] r = requests.get(url2, allow_redirects=True) apparent_encoding = r.apparent_encoding r.encoding = r.apparent_encoding inpt = r.text lines = inpt.splitlines() inpt = [l.split('\t') for l in lines] obench_dict = {} for line in inpt: if len(line[0]) > 0: if line[0][0] == '[': # process new section header, initialize input list key = line[0][1:-1] obench_dict[key] = [] else: # add input to the currect section's list of inputs obench_dict[key].append(line) _track_contents['encoding'] = apparent_encoding # type: ignore _track_contents['obench db'] = obench_dict # type: ignore return lines, obench_dict
[docs] def read_lens(inpts, opt_model=None) -> OpticalModel: global _track_contents def read_float(s): if s == 'Infinity': return float('inf') elif isanumber(s): return float(s) else: if s == 'undefined': return float('nan') elif s == 'AS': # aperture stop return 0. elif s == 'FS': # field stop return 0. elif s == 'CG': # cover glass return 0. elif s == '': return 0. else: try: return float(read_float(var_dists[s][0])) except: return 0. _track_contents = (util.Counter() if _track_contents is None else _track_contents) if "constants" in inpts: constants_inpt = inpts['constants'] constants = {c_item[0]: c_item[1:] for c_item in constants_inpt} else: constants = {} if "variable distances" in inpts: var_dists_inpt = inpts['variable distances'] var_dists = {var_dist[0]: var_dist[1:] for var_dist in var_dists_inpt} else: var_dists = {} thi_obj = 1.0e10 if 'd0' in var_dists: thi_obj = read_float(var_dists['d0'][0]) if thi_obj == float('inf'): thi_obj = 1.0e10 conj_type = 'finite' if is_kinda_big(thi_obj): conj_type = 'infinite' _track_contents['conj type'] = conj_type if opt_model is None: opt_model = OpticalModel(do_init=True) opt_model.radius_mode = True sm = opt_model['seq_model'] sm.do_apertures = False sm.gaps[0].thi = thi_obj if 'ObjectGlass' in constants: nd_obj = read_float(constants['ObjectGlass'][0]) vd_obj = read_float(constants['ObjectGlass'][1]) sm.gaps[0].medium = mg.ModelGlass(nd_obj, vd_obj, 'ObjectGlass') osp = opt_model['optical_spec'] if 'F-Number' in var_dists: osp['pupil'].key = ('image', 'f/#') osp['pupil'].value = read_float(var_dists['F-Number'][0]) elif 'NA' in var_dists: osp['pupil'].key = ('object', 'NA') osp['pupil'].value = read_float(var_dists['NA'][0]) if 'Image Height' in var_dists: img_ht = read_float(var_dists['Image Height'][0])/2 if 'Angle of View' in var_dists: angle_of_view = read_float(var_dists['Angle of View'][0]) osp['fov'].is_wide_angle = True if angle_of_view/2 > 45. else False osp['fov'].key = ('image', 'real height') osp['fov'].value = img_ht if 'Magnification' in var_dists: mag = read_float(var_dists['Magnification'][0]) if not is_fuzzy_zero(mag): osp['fov'].key = ('object', 'height') osp['fov'].value = img_ht/mag osp['fov'].is_relative = True osp['fov'].set_from_list([0., .707, 1.]) osp['wvls'] = WvlSpec(wlwts=[('F', .5), ('d', 1.), ('C', .5)], ref_wl=1) surfaces = {} # map surface ID to surface num, surface ID is not always a sequence number if 'lens data' in inpts: input_lines = inpts['lens data'] _track_contents['# surfs'] = len(input_lines) for line in input_lines: surf_id = line[0] radius = read_float(line[1]) thickness = read_float(line[2]) nd = line[3] vd = line[5] if len(line) > 5 else '' diam = read_float(line[4]) glass_name = line[6] if len(line) > 6 else '' glass_make = line[7] if len(line) > 7 else '' inpt = [radius, thickness] # --- Determine optical material fields --- if nd == '': # Case 1: missing nd → push two blanks inpt.extend(['', '']) elif glass_name and glass_make: # Case 2: we have explicit glass name/make inpt.extend([glass_name,glass_make]) else: # Case 3: numeric nd / vd inpt.append(read_float(nd)) # nd if vd != '': inpt.append(read_float(vd)) # vd sm.add_surface(inpt, sd=diam/2) if line[1] == 'AS': sm.set_stop() surfaces[surf_id] = sm.cur_surface # note surface num if 'aspherical data' in inpts: if 'AsphericalOddCount' in constants: typ = 'AsphericalOddCount' elif 'AsphericalA2' in constants: typ = 'AsphericalA2' else: typ = 'Aspherical' input_lines = inpts['aspherical data'] _track_contents[typ] = len(input_lines) for line in input_lines: surf_id = line[0] if typ == 'AsphericalOddCount': asp_coefs = [read_float(item) for item in line[3:]] asp_coefs = [0., 0.] + asp_coefs elif typ == 'AsphericalA2': asp_coefs = [read_float(item) for item in line[3:]] else: asp_coefs = [read_float(item) for item in line[2:]] asp_coefs[0] = 0. if typ == 'AsphericalOddCount': asp = RadialPolynomial(r=read_float(line[1]), cc=read_float(line[2]), coefs=asp_coefs) else: asp = EvenPolynomial(r=read_float(line[1]), cc=read_float(line[2]), coefs=asp_coefs) idx = surfaces[surf_id] # lookup surface number using surface ID sm.ifcs[idx].profile = asp if 'diffractive data' in inpts: input_lines = inpts['diffractive data'] _track_contents['# doe'] = len(input_lines) for line in input_lines: surf_id = line[0] coefs = [read_float(item) for item in line[3:]] dif_elem = DiffractiveElement(coefficients=coefs, ref_wl=read_float(line[1]), order=read_float(line[2]), phase_fct=doe.radial_phase_fct) idx = surfaces[surf_id] # lookup surface number using surface ID sm.ifcs[idx].phase_element = dif_elem if 'descriptive data' in inpts: input_lines = inpts['descriptive data'] descripts = {input_line[0]: input_line[1:] for input_line in input_lines} if 'title' in descripts: opt_model['sys'].title = descripts['title'][0] return opt_model