Skip to content

Enhance aspheric surface management and optimize lens design#111

Merged
singer-yang merged 31 commits intodevfrom
aspheric_optim
Feb 28, 2026
Merged

Enhance aspheric surface management and optimize lens design#111
singer-yang merged 31 commits intodevfrom
aspheric_optim

Conversation

@singer-yang
Copy link
Copy Markdown
Collaborator

This pull request introduces several improvements and refactors across the lens design codebase, focusing on configuration consistency, code organization, and enhancements to lens data files. The most significant changes include standardizing parameter names in configuration and code, updating the surface types and features in lens JSON files, and improving import structure and module exports for better maintainability and clarity.

Configuration and Parameter Standardization

  • Replaced the flange parameter with bfl (back focal length) in both the YAML configuration file (configs/2_auto_lens_design.yml) and the codebase, ensuring consistent terminology and usage throughout the project. [1] [2]
  • Removed the decay parameter from optimizer-related function calls and configuration, simplifying the optimizer setup in 2_autolens_rms.py and the YAML config. [1] [2] [3] [4] [5]

Lens Data File Enhancements

Code Organization and Module Exports

  • Refactored imports in deeplens/optics/__init__.py to explicitly import only the necessary classes and functions, and defined the __all__ variable to clarify the public API of the optics module. [1] [2]
  • Updated imports in deeplens/optics/geolens.py to use new module names for Seidel evaluation, surface operations, tolerance analysis, and 3D visualization, reflecting codebase restructuring and improved modularity.

Optical Wavelength Definitions

  • Added Fraunhofer wavelength constants (WVLN_d, WVLN_F, WVLN_C) to deeplens/optics/config.py for standard chromatic aberration reference, supporting more accurate optical calculations.

Example and Analysis Improvements

  • Updated the example in 0_hello_deeplens.py to use the cellphone lens JSON and enabled full_eval in the analysis for more comprehensive evaluation.

singer-yang and others added 30 commits February 27, 2026 01:44
Implement two new methods for progressive aspheric surface management
during differentiable lens design optimization:

- add_aspheric: converts Spheric to Aspheric with auto-selection of
  best candidate based on aperture stop proximity and air-glass
  interface heuristics (following classical design principles)
- increase_aspheric_order: incrementally adds higher-order polynomial
  terms to existing Aspheric surfaces

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both are physically identical to air (n=1). Material.__init__ now maps
legacy names to "air" at init time, keeping backward compatibility with
existing lens JSON files. Removed redundant entries from materials_data.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 2nd-order aspheric coefficient (a2) competes with base curvature c
and should not be optimised. Change ai list to [a4, a6, a8, ...] across
Aspheric, AsphericNorm, and _increase_surface_order. Old JSON files with
a2 are handled transparently via the use_ai2 backward-compat flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When surf_idx is not given, pick ONE best aspheric surface instead of
increasing all. Selection ranks by lowest ai_degree, then largest
semi-diameter (marginal ray height proxy), then highest Δn at interface.
Follows Principle 5: one surface, one term at a time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New standalone script (9_autolens_aspheric.py) that starts from
all-spherical surfaces, gradually expands aperture, and introduces
aspheric surfaces and higher-order terms at stage boundaries. Uses
add_aspheric and increase_aspheric_order for staged aspherization.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 2nd asphere auto-selection picked surface 0 (front element), which
is impractical in camera lens design. Now _find_best_asphere_candidate
excludes the first and last refractive surfaces from subsequent asphere
selection, falling back to the full list only when no inner candidates
remain. Updated design principles doc with the outermost-surface rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move aspheric surface management (add_aspheric, increase_aspheric_order
and helpers) from GeoLensOptim and surface pruning/shape correction
(prune_surf, correct_shape) from GeoLens into a dedicated
GeoLensSurfOps mixin, keeping GeoLensOptim focused on loss functions
and gradient-based optimisation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lization

New GeoLensSeidel mixin computes per-surface third-order (Seidel)
aberration coefficients via paraxial ray tracing and renders a
Zemax-style grouped bar chart. Supports both Aspheric and AsphericNorm
surfaces, and includes chromatic aberration (C_L, C_T).

Co-Authored-By: Claude <noreply@anthropic.com>
Moved WVLN_d, WVLN_F, and WVLN_C constants from seidel.py to
config.py to centralize optical configuration parameters. Updated
imports in seidel.py to use the new location.
… failures

Rename mixin files for consistent naming:
- seidel.py → eval_seidel.py
- tolerance.py → eval_tolerance.py
- surf_ops.py → optim_ops.py
- view_3d.py → vis3d.py

Move match_materials() from geolens.py to optim_ops.py. Update all
imports, docstrings, and documentation references.

Fix three pre-existing test issues:
- test_material_default: expect "air" (Material normalizes None to "air")
- test_aspheric_higher_order: expect ai4*r^4 (ai list starts at a4, not a2)
- test_read_zmx: use available ef35mm_f2.0.zmx fixture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace ~55 print() statements with proper logging using module-level
loggers across 12 files. Fix bare except: clause in io.py with specific
exception types. Fix root logger configuration in optim.py to use
module-level logger.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove redundant z and point_source tensor creation inside the
if quater: block that duplicated lines already executed above.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…add __all__

Replace from .module import * with explicit named imports in 6
__init__.py files. Add __all__ to all package __init__.py files
for explicit public API definition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add random parameter sampling across three lens categories (camera
spheric, camera aspheric, mobile) with SLURM GPU job and CPU dispatcher
scripts for running large-scale automated lens design experiments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… for loading

The EFL calculation was returning unreasonable values (~139mm instead of ~5mm)
for cellphone lenses because:
1. io.py loaded "Aspheric" JSON data as AsphericNorm instead of Aspheric
2. The a2 (2nd-order) aspheric coefficient was silently discarded, losing ~84%
   of the paraxial curvature for lenses that rely on a2

Changes:
- Aspheric: add ai2 parameter, include a2*r² in sag and derivative computation
- io.py: use Aspheric.init_from_dict() for "Aspheric" type surfaces
- Preserve ai2 through save/load round-trips and Zemax export (PARM 1)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove `logging` module import and `logger` instance. Convert the debug message regarding legacy format handling from `logger.debug` to a standard `print` statement using an f-string.
Replace sequential per-field-angle ray tracing with batched GPU
computation. All field angles are packed into a single [F, spp, 3]
tensor and traced in one call per wavelength (3 calls vs 96).
Add parabolic interpolation for sub-grid defocus precision, boundary
warnings, and NaN masking for fully-vignetted field angles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Vignetting: use uniform_fov=False for correct sensor-space mapping
  (was distorted for wide-angle lenses), reduce rays 16384→512 and
  grid 64→32 (~130x speedup), remove 0.5+0.5x scaling that compressed
  dynamic range, show full [0,1] colorbar
- Distortion: skip near-zero FOV angles to avoid numerical instability
  in ray aiming, prepend FOV=0 with distortion=0 by definition, add
  min_delta floor in chief ray search to prevent degenerate range
- Fix pre-existing bug in calc_chief_ray_infinite where hardcoded [1:]
  indexing failed when input had no zero-angle entry (use dynamic
  start_idx)
- Add @torch.no_grad() to vignetting, distortion, and analysis_spot
  functions for memory efficiency
- Use consistent calc_entrance_pupil() call in ray aiming

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Noise modeling does not belong in the optics simulation layer. Remove
the noise parameter and Gaussian noise injection from analysis_rendering
and its caller in analysis(). Sensor noise is handled separately by the
Camera class.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The distortion at FOV=0 was hardcoded to 0%, but the true limit can be
non-zero when the sensor is defocused from the paraxial focal plane.
Replace the split sampling (skip small FOV + prepend 0) with uniform
sampling and compute the on-axis point at a tiny angle to get the
correct limit value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove redundant enpd parameter (derived from foclen/fnum)
- Rename flange → bfl (back focal length) to match standard optics
  terminology and existing self.bfl in geolens.py
- Allow specifying either foclen or imgh (mutually exclusive), with
  the other derived via imgh = 2 * foclen * tan(fov/2)
- Rename flange_min/flange_max → bfl_min/bfl_max in optim.py
- Update all call sites (4 files)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove AsphericNorm and WAVE_RGB imports as they are not utilized in the module.
… radius

Aspheric._sag() computes ai_{2n} * r^{2n} with raw coordinates, so
gradients scale as O(r^{2n}).  For camera lenses (r=20-25mm) this
produces gradient magnitudes of ~10^5, causing NaN within ~40 iterations.

Additionally, prune_surf oscillates surface semi-diameters between
evaluation steps (e.g. 5.3mm -> 24.9mm), causing ~500x gradient swings
that corrupt Adam's second-moment estimates and lead to 30x oversized
steps.

Scale each ai coefficient's lr by 1/max(r,1)^{2n} so the effective sag
perturbation per Adam step is constant (~lr_base mm) regardless of
surface size.  The old per-order decay is removed since the r-scaling
already normalises across orders, matching AsphericNorm's constant-lr
behaviour.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
set_target_fov_fnum() and set_fov() overwrote self.rfov without
recomputing self.eqfl, leaving the stale value from initial lens
load.  This caused layout titles to display wrong equivalent focal
lengths (e.g. 1838mm instead of ~34mm).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the `decay` parameter from the entire aspheric optimization
chain (optimize, get_optimizer, get_optimizer_params) since lr
normalisation by 1/r^{2n} already balances all aspheric orders.

Also rename remaining `flange` references to `bfl` in configs and
scripts for consistency with the create_lens API change, and update
cellphone lens datasets with use_ai2 flag and Aperture type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AsphericNorm is no longer needed — Aspheric.get_optimizer_params now
applies per-order lr normalization (lr / r^{2n}), achieving the same
gradient stability. Batch results confirm 0 crashes with Aspheric alone.

- Delete aspheric_norm.py
- Remove imports and isinstance checks from optim, optim_ops, eval_seidel, io
- Remove TestAsphericNorm from test suite
- Update README, CLAUDE.md, and example scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document recent breaking changes: flange→bfl rename, enpd removal,
foclen/imgh mutual exclusivity in create_lens, and decay parameter
removal from the optimization API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add CLAUDE.md to .gitignore — it contains local project notes
that should not be committed to the repository.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@singer-yang singer-yang merged commit 167c580 into dev Feb 28, 2026
@singer-yang singer-yang deleted the aspheric_optim branch April 4, 2026 14:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant