Skip to content

Commit 093e1ca

Browse files
Fix/image space non air material (#76)
* Add final lens component to optical system if present * Update path length calculations to include wavelength parameter * chore: Add inverse transformation for global coordinate system in image surface * feat: Enhance SurfaceFactory to support image surface creation and material configuration * test: Add unit tests for image surface generation in SurfaceFactory * fix: Update ImageSurface constructor to correctly assign material_post parameter * refactor: Simplify surface creation logic in SurfaceFactory by removing image surface handling * fix: Correct refractive index assignment in Wavefront class to use material_post parameter * refactor: Remove obsolete image surface generation tests from TestSurfaceFactory * fix: Update ImageSurface tests to correctly handle material_post parameter * refactor: Remove redundant exit pupil location calculation in Paraxial class * fix: Update path length and OPD image calculations in Wavefront tests to include wavelength parameter * chore: Add TODO for tests on non-air object and image spaces in Paraxial tests
1 parent 02d57db commit 093e1ca

File tree

8 files changed

+42
-23
lines changed

8 files changed

+42
-23
lines changed

optiland/paraxial.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,17 +172,10 @@ def XPL(self):
172172
float: exit pupil location relative to the image surface
173173
"""
174174
stop_index = self.surfaces.stop_index
175-
num_surfaces = len(self.surfaces.surfaces)
176-
if stop_index == num_surfaces-2:
177-
positions = self.optic.surface_group.positions
178-
loc_relative = positions[-2] - positions[-1]
179-
return loc_relative[0]
180-
181175
z_start = self.surfaces.positions[stop_index]
182176
wavelength = self.optic.primary_wavelength
183177
y, u = self._trace_generic(0.0, 0.1, z_start, wavelength,
184178
skip=stop_index+1)
185-
186179
loc_relative = -y[-1] / u[-1]
187180
return loc_relative[0]
188181

optiland/surfaces/image_surface.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ class ImageSurface(Surface):
2525
"""
2626

2727
def __init__(self, geometry: BaseGeometry, material_pre: BaseMaterial,
28-
aperture: BaseAperture = None):
28+
material_post: BaseMaterial, aperture: BaseAperture = None):
2929
super().__init__(
3030
geometry=geometry,
3131
material_pre=material_pre,
32-
material_post=material_pre,
32+
material_post=material_post,
3333
is_stop=False,
3434
aperture=aperture
3535
)
@@ -51,8 +51,13 @@ def _trace_paraxial(self, rays: ParaxialRays):
5151
t = -rays.z
5252
rays.propagate(t)
5353

54+
# inverse transform coordinate system
55+
self.geometry.globalize(rays)
56+
5457
self._record(rays)
5558

59+
return rays
60+
5661
def _interact(self, rays):
5762
"""
5863
Interacts rays with the surface.

optiland/surfaces/surface_factory.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(self, surface_group):
4444
self._surface_group = surface_group
4545
self.last_thickness = 0
4646
self._use_absolute_cs = False
47+
self._last_surface = None # placeholder for last surface object
4748

4849
def create_surface(self, surface_type, index, is_stop, material,
4950
**kwargs):
@@ -318,7 +319,7 @@ def _configure_zernike_geometry(self, cs, **kwargs):
318319
norm_radius)
319320

320321
return geometry
321-
322+
322323
def _configure_material(self, index, material):
323324
"""
324325
Configures the material for a surface based on the given index and
@@ -335,8 +336,16 @@ def _configure_material(self, index, material):
335336
tuple: A tuple containing the material before and after the
336337
surface.
337338
"""
339+
# object surface
338340
if index == 0:
339341
material_pre = None
342+
343+
# image surface
344+
elif (index == self._surface_group.num_surfaces
345+
and self._last_surface is not None):
346+
material_pre = self._last_surface.material_post
347+
348+
# intermediate surface
340349
else:
341350
previous_surface = self._surface_group.surfaces[index-1]
342351
material_pre = previous_surface.material_post

optiland/visualization/system.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ def _identify_components(self):
103103

104104
lens_surfaces = []
105105

106+
# add final lens, if any
107+
if lens_surfaces:
108+
self._add_component('lens', lens_surfaces)
109+
106110
def _add_component(self, component_name, *args):
107111
"""
108112
Adds a component to the list of components.

optiland/wavefront.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def _generate_data(self, fields, wavelengths):
8686

8787
# Reference sphere center and radius
8888
xc, yc, zc, R = self._get_reference_sphere(pupil_z)
89-
opd_ref = self._get_path_length(xc, yc, zc, R)
89+
opd_ref = self._get_path_length(xc, yc, zc, R, wavelength)
9090
opd_ref = self._correct_tilt(field, opd_ref, x=0, y=0)
9191

9292
field_data.append(self._generate_field_data(field, wavelength,
@@ -116,7 +116,7 @@ def _generate_field_data(self, field, wavelength, opd_ref, xc, yc, zc, R):
116116
# trace distribution through pupil
117117
self.optic.trace(*field, wavelength, None, self.distribution)
118118
intensity = self.optic.surface_group.intensity[-1, :]
119-
opd = self._get_path_length(xc, yc, zc, R)
119+
opd = self._get_path_length(xc, yc, zc, R, wavelength)
120120
opd = self._correct_tilt(field, opd)
121121
return (opd_ref - opd) / (wavelength * 1e-3), intensity
122122

@@ -159,7 +159,7 @@ def _get_reference_sphere(self, pupil_z):
159159

160160
return xc, yc, zc, R
161161

162-
def _get_path_length(self, xc, yc, zc, r):
162+
def _get_path_length(self, xc, yc, zc, r, wavelength):
163163
"""
164164
Calculates the optical path difference.
165165
@@ -168,12 +168,13 @@ def _get_path_length(self, xc, yc, zc, r):
168168
yc (float): The y-coordinate of the reference sphere center.
169169
zc (float): The z-coordinate of the reference sphere center.
170170
r (float): The radius of the reference sphere.
171+
wavelength (float): The wavelength of the light.
171172
172173
Returns:
173174
float: The optical path difference.
174175
"""
175176
opd = self.optic.surface_group.opd[-1, :]
176-
return opd - self._opd_image_to_xp(xc, yc, zc, r)
177+
return opd - self._opd_image_to_xp(xc, yc, zc, r, wavelength)
177178

178179
def _correct_tilt(self, field, opd, x=None, y=None):
179180
"""
@@ -203,7 +204,7 @@ def _correct_tilt(self, field, opd, x=None, y=None):
203204
(1 - y) * np.sin(np.radians(y_tilt)) * EPD / 2)
204205
return opd - tilt_correction
205206

206-
def _opd_image_to_xp(self, xc, yc, zc, R):
207+
def _opd_image_to_xp(self, xc, yc, zc, R, wavelength):
207208
"""
208209
Finds propagation distance from image plane to reference sphere.
209210
@@ -212,6 +213,7 @@ def _opd_image_to_xp(self, xc, yc, zc, R):
212213
yc (float): The y-coordinate of the reference sphere center.
213214
zc (float): The z-coordinate of the reference sphere center.
214215
R (float): The radius of the reference sphere.
216+
wavelength (float): The wavelength of the light.
215217
216218
Returns:
217219
float: Propagation distance from image plane to reference sphere.
@@ -232,7 +234,10 @@ def _opd_image_to_xp(self, xc, yc, zc, R):
232234
d = b ** 2 - 4 * a * c
233235
t = (-b - np.sqrt(d)) / (2 * a)
234236
t[t < 0] = (-b[t < 0] + np.sqrt(d[t < 0])) / (2 * a[t < 0])
235-
return t
237+
238+
# refractive index in image space
239+
n = self.optic.image_surface.material_post.n(wavelength)
240+
return n * t
236241

237242

238243
class OPDFan(Wavefront):

tests/test_image_surface.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,26 @@ def setup_image_surface():
1212
cs = CoordinateSystem()
1313
geometry = StandardGeometry(cs, 100)
1414
material_pre = IdealMaterial(1, 0)
15+
material_post = IdealMaterial(1, 0)
1516
image_surface = ImageSurface(
1617
geometry=geometry,
1718
material_pre=material_pre,
19+
material_post=material_post,
1820
aperture=None
1921
)
20-
return image_surface, geometry, material_pre
22+
return image_surface, geometry, material_pre, material_post
2123

2224

2325
def test_initialization(setup_image_surface):
24-
image_surface, geometry, material_pre = setup_image_surface
26+
image_surface, geometry, material_pre, material_post = setup_image_surface
2527
assert image_surface.geometry == geometry
2628
assert image_surface.material_pre == material_pre
27-
assert image_surface.material_post == material_pre
29+
assert image_surface.material_post == material_post
2830
assert not image_surface.is_stop
2931

3032

3133
def test_trace_paraxial(setup_image_surface):
32-
image_surface, _, _ = setup_image_surface
34+
image_surface, _, _, _ = setup_image_surface
3335
y = np.array([1])
3436
u = np.array([0])
3537
z = np.array([-10])
@@ -39,7 +41,7 @@ def test_trace_paraxial(setup_image_surface):
3941

4042

4143
def test_interact(setup_image_surface):
42-
image_surface, _, _ = setup_image_surface
44+
image_surface, _, _, _ = setup_image_surface
4345
x = np.random.rand(10)
4446
rays = RealRays(x, x, x, x, x, x, x, x)
4547
modified_rays = image_surface._interact(rays)

tests/test_paraxial.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
PetzvalLens,
2424
Telephoto
2525
)
26+
# TODO: add tests for non-air object and image spaces
2627

2728

2829
def get_optic_data():

tests/test_wavefront.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def test_get_path_length(self):
6767
w = wavefront.Wavefront(optic)
6868
w._trace_chief_ray((0, 0), 0.55)
6969
xc, yc, zc, R = w._get_reference_sphere(pupil_z=100)
70-
path_length = w._get_path_length(xc, yc, zc, R)
70+
path_length = w._get_path_length(xc, yc, zc, R, 0.55)
7171
assert np.allclose(path_length, np.array([34.84418309]))
7272

7373
def test_correct_tilt(self):
@@ -86,7 +86,7 @@ def test_opd_image_to_xp(self):
8686
w = wavefront.Wavefront(optic)
8787
w._trace_chief_ray((0, 0), 0.55)
8888
xc, yc, zc, R = w._get_reference_sphere(pupil_z=100)
89-
t = w._opd_image_to_xp(xc, yc, zc, R)
89+
t = w._opd_image_to_xp(xc, yc, zc, R, 0.55)
9090
assert np.allclose(t, np.array([39.454938]))
9191

9292

0 commit comments

Comments
 (0)