• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brandondube / prysm / 74614c71-98d8-4dbe-b2c3-1f53a177e8b9

25 May 2026 05:10AM UTC coverage: 82.929% (+1.1%) from 81.804%
74614c71-98d8-4dbe-b2c3-1f53a177e8b9

push

circleci

brandondube
tests/x/raytracing: avoid MD5 hash equality in tests, use array allclose

4926 of 5940 relevant lines covered (82.93%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.45
/prysm/thinlens.py
1
"""First-order optics equations for system modeling."""
2

3
from .mathops import np
1✔
4

5

6
def object_to_image_dist(efl, object_distance):
1✔
7
    """Compute the image distance from the object distance.
8

9
    Parameters
10
    ----------
11
    efl : float
12
        focal length of the lens
13
    object_distance : float or ndarray
14
        distance from the object to the front principal plane of the lens,
15
        negative for an object to the left of the lens
16

17
    Returns
18
    -------
19
    float
20
        image distance.  Distance from rear principal plane (assumed to be in
21
        contact with front principal plane) to image.
22

23
    Notes
24
    -----
25
    efl and object distance should be in the same units.  Return value will
26
    be in the same units as the inputs.
27

28
    """
29
    ret = 1 / efl + 1 / object_distance
1✔
30
    return 1 / ret
1✔
31

32

33
def image_to_object_dist(efl, image_distance):
1✔
34
    """Compute the object distance from the image distance.
35

36
    Parameters
37
    ----------
38
    efl : float
39
        focal length of the lens
40
    image_distance : float or ndarray
41
        distance from the object to the front principal plane of the lens,
42
        positive for an object in front of a lens of positive focal length.
43

44
    Notes
45
    -----
46
    efl and image distance should be in the same units.  Return value will
47
    be in the same units as the input.
48

49
    """
50
    ret = 1 / efl - 1 / image_distance
×
51
    return 1 / ret
×
52

53

54
def object_image_to_efl(object_distance, image_distance):
1✔
55
    """Compute focal length from a pair of conjugate distances.
56

57
    Parameters
58
    ----------
59
    object_distance : float or ndarray
60
        signed object distance from the front principal plane
61
    image_distance : float or ndarray
62
        signed image distance from the rear principal plane
63

64
    Returns
65
    -------
66
    float or ndarray
67
        focal length, in the same units as the inputs
68

69
    """
70
    power = 1 / image_distance - 1 / object_distance
1✔
71
    return 1 / power
1✔
72

73

74
def efl_to_power(efl, n=1):
1✔
75
    """Convert effective focal length to optical power.
76

77
    Parameters
78
    ----------
79
    efl : float or ndarray
80
        effective focal length
81
    n : float, optional
82
        refractive index of the surrounding medium
83

84
    Returns
85
    -------
86
    float or ndarray
87
        optical power, in inverse units of efl
88

89
    """
90
    return n / efl
1✔
91

92

93
def power_to_efl(power, n=1):
1✔
94
    """Convert optical power to effective focal length.
95

96
    Parameters
97
    ----------
98
    power : float or ndarray
99
        optical power
100
    n : float, optional
101
        refractive index of the surrounding medium
102

103
    Returns
104
    -------
105
    float or ndarray
106
        effective focal length
107

108
    """
109
    return n / power
1✔
110

111

112
def efl_to_fno(efl, epd):
1✔
113
    """Compute f/# from effective focal length and entrance pupil diameter.
114

115
    Parameters
116
    ----------
117
    efl : float or ndarray
118
        effective focal length
119
    epd : float
120
        entrance pupil diameter
121

122
    Returns
123
    -------
124
    float or ndarray
125
        f/number
126

127
    """
128
    return abs(efl) / epd
1✔
129

130

131
def fno_to_efl(fno, epd):
1✔
132
    """Compute effective focal length from f/# and entrance pupil diameter.
133

134
    Parameters
135
    ----------
136
    fno : float or ndarray
137
        f/number
138
    epd : float
139
        entrance pupil diameter
140

141
    Returns
142
    -------
143
    float or ndarray
144
        effective focal length
145

146
    """
147
    return fno * epd
1✔
148

149

150
def fno_to_epd(fno, efl):
1✔
151
    """Compute entrance pupil diameter from f/# and effective focal length.
152

153
    Parameters
154
    ----------
155
    fno : float or ndarray
156
        f/number
157
    efl : float
158
        effective focal length
159

160
    Returns
161
    -------
162
    float or ndarray
163
        entrance pupil diameter
164

165
    """
166
    return abs(efl) / fno
1✔
167

168

169
def image_dist_epd_to_na(image_distance, epd):
1✔
170
    """Compute the NA from an image distance and entrance pupil diameter.
171

172
    Parameters
173
    ----------
174
    image_distance : float
175
        distance from the image to the entrance pupil
176
    epd : float
177
        diameter of the entrance pupil
178

179
    Returns
180
    -------
181
    float
182
        numerical aperture.  The NA of the system.
183

184
    """
185
    rho = epd / 2
1✔
186
    marginal_ray_angle = abs(np.arctan2(rho, image_distance))
1✔
187
    return marginal_ray_angle
1✔
188

189

190
def image_dist_epd_to_fno(image_distance, epd):
1✔
191
    """Compute the f/# from an image distance and entrance pupil diameter.
192

193
    Parameters
194
    ----------
195
    image_distance : float
196
        distance from the image to the entrance pupil
197
    epd : float
198
        diameter of the entrance pupil
199

200
    Returns
201
    -------
202
    float
203
        fno.  The working f/# of the system.
204

205
    """
206
    na = image_dist_epd_to_na(image_distance, epd)
1✔
207
    return na_to_fno(na)
1✔
208

209

210
def fno_to_na(fno):
1✔
211
    """Convert an fno to an NA.
212

213
    Parameters
214
    ----------
215
    fno : float
216
        focal ratio
217

218
    Returns
219
    -------
220
    float
221
        NA.  The NA of the system.
222

223
    """
224
    return 1 / (2 * fno)
1✔
225

226

227
def na_to_fno(na):
1✔
228
    """Convert an NA to an f/#.
229

230
    Parameters
231
    ----------
232
    na : float
233
        numerical aperture
234

235
    Returns
236
    -------
237
    float
238
        fno.  The f/# of the system.
239

240
    """
241
    return 1 / (2 * np.sin(na))
1✔
242

243

244
def object_dist_to_mag(efl, object_dist):
1✔
245
    """Compute the linear magnification from the object distance and focal length.
246

247
    Parameters
248
    ----------
249
    efl : float
250
        focal length of the lens
251
    object_dist : float
252
        object distance
253

254
    Returns
255
    -------
256
    float
257
        linear magnification.  Also known as the lateral magnification
258

259
    """
260
    return efl / (efl - object_dist)
1✔
261

262

263
def mag_to_object_dist(efl, mag):
1✔
264
    """Compute the object distance for a given focal length and magnification.
265

266
    Parameters
267
    ----------
268
    efl : float
269
        focal length of the lens
270
    mag : float
271
        signed magnification
272

273
    Returns
274
    -------
275
    float
276
        object distance
277

278
    """
279
    return efl * (1 - 1/mag)
1✔
280

281

282
def mag_to_image_dist(efl, mag):
1✔
283
    """Compute the image distance for a given focal length and magnification.
284

285
    Parameters
286
    ----------
287
    efl : float
288
        focal length of the lens
289
    mag : float
290
        signed magnification
291

292
    Returns
293
    -------
294
    float
295
        image distance
296

297
    """
298
    return efl * (1 - mag)
1✔
299

300

301
def linear_to_long_mag(lateral_mag):
1✔
302
    """Compute the longitudinal (along optical axis) magnification from the lateral mag.
303

304
    Parameters
305
    ----------
306
    lateral_mag : float
307
        linear magnification, from thin lens formulas
308

309
    Returns
310
    -------
311
    float
312
        longitudinal magnification
313

314
    """
315
    return lateral_mag**2
1✔
316

317

318
def mag_to_fno(mag, infinite_fno, pupil_mag=1):
1✔
319
    """Compute the working f/# from the magnification and infinite f/#.
320

321
    Parameters
322
    ----------
323
    mag : float or ndarray
324
        linear or lateral magnification
325
    infinite_fno : float
326
        f/# as defined by EFL/EPD
327
    pupil_mag : float
328
        pupil magnification
329

330
    Returns
331
    -------
332
    float
333
        working f/number
334

335
    """
336
    return (1 + abs(mag) / pupil_mag) * infinite_fno
1✔
337

338

339
def defocus_to_image_displacement(W020, fno, wavelength=None):
1✔
340
    """Compute image displacment from wavefront defocus expressed in waves 0-P to.
341

342
    Parameters
343
    ----------
344
    W020 : float or ndarray
345
        wavefront defocus, units of waves if wavelength != None, else units of length
346
    fno : float
347
        f/# of the lens or system
348
    wavelength : float, optional
349
        wavelength of light, if None W020 takes units of length
350

351
    Returns
352
    -------
353
    float
354
        image displacement.  Motion of image in um caused by defocus OPD
355

356
    """
357
    if wavelength is not None:
1✔
358
        return 8 * fno**2 * wavelength * W020
1✔
359
    else:
360
        return 8 * fno**2 * W020
1✔
361

362

363
def image_displacement_to_defocus(dz, fno, wavelength=None):
1✔
364
    """Compute the wavefront defocus from image shift, expressed in the same units as the shift.
365

366
    Parameters
367
    ----------
368
    dz : float or ndarray
369
        displacement of the image
370
    fno : float
371
        f/# of the lens or system
372
    wavelength : float, optional
373
        wavelength of light, if None return has units the same as dz, else waves
374

375
    Returns
376
    -------
377
    float
378
        wavefront defocus, waves if Wavelength != None, else same units as dz
379

380
    """
381
    if wavelength is not None:
1✔
382
        return dz / (8 * fno ** 2 * wavelength)
1✔
383
    else:
384
        return dz / (8 * fno ** 2)
1✔
385

386

387
def image_shift_to_tilt(dx, fno):
1✔
388
    """Compute the wavefront tilt associated with an image shift.
389

390
    Parameters
391
    ----------
392
    dx : float or ndarray
393
        translation of the image
394
    fno : float
395
        f/# of the lens or system
396

397
    Returns
398
    -------
399
    float
400
        wavefront tilt W111, same units as dx
401
        W111 has a peak-to-valley of 2, and "amplitude" of 1
402
        to convert to Z2 or Z3, those have a peak-to-valley of 4, so
403
        divide by two for amplitude coefficients, or 4 for RMS coefficients
404

405
    """
406
    return (dx/fno)*0.5
×
407

408

409
def tilt_to_image_shift(W111, fno):
1✔
410
    """Compute image shift from wavefront tilt.
411

412
    Parameters
413
    ----------
414
    W111 : float or ndarray
415
        wavefront tilt, unit amplitude (peak-to-valley of 2)
416
    fno : float
417
        f/# of the lens or system
418

419
    Returns
420
    -------
421
    float
422
        image translation, in same units as W111 (e.g., um)
423

424
    """
425
    return 2*(W111*fno)
×
426

427

428
def singlet_power(c1, c2, t, n, n_ambient=1.):
1✔
429
    """Optical power of a thick singlet.
430

431
    Parameters
432
    ----------
433
    c1 : float
434
        curvature of S1
435
    c2 : float
436
        curvature of S2
437
    t : float
438
        vertex-to-vertex thickness
439
    n : float
440
        refractive index
441
    n_ambient: float
442
        refractive index of the ambient medium ("air")
443

444
    Returns
445
    -------
446
    float
447
        optical power in the ambient medium
448

449
    """
450
    phi1 = (n - n_ambient) * c1
1✔
451
    phi2 = (n_ambient - n) * c2
1✔
452
    return phi1 + phi2 - t/n * phi1 * phi2
1✔
453

454

455
def singlet_efl(c1, c2, t, n, n_ambient=1.):
1✔
456
    """EFL of a singlet.
457

458
    Parameters
459
    ----------
460
    c1 : float
461
        curvature of S1
462
    c2 : float
463
        curvature of S2
464
    t : float
465
        vertex-to-vertex thickness
466
    n : float
467
        refractive index
468
    n_ambient: float
469
        refractive index of the ambient medium ("air")
470

471
    Returns
472
    -------
473
    float
474
        EFL
475

476
    """
477
    phi = singlet_power(c1, c2, t, n, n_ambient)
1✔
478
    return n_ambient / phi
1✔
479

480

481
def singlet_bfl(c1, c2, t, n, n_ambient=1.):
1✔
482
    """Back focal length of a thick singlet.
483

484
    Parameters
485
    ----------
486
    c1 : float
487
        curvature of S1
488
    c2 : float
489
        curvature of S2
490
    t : float
491
        vertex-to-vertex thickness
492
    n : float
493
        refractive index
494
    n_ambient: float
495
        refractive index of the ambient medium ("air")
496

497
    Returns
498
    -------
499
    float
500
        signed distance from S2 to the rear focal point
501

502
    """
503
    phi1 = (n - n_ambient) * c1
1✔
504
    efl = singlet_efl(c1, c2, t, n, n_ambient)
1✔
505
    return efl * (1 - t/n * phi1)
1✔
506

507

508
def singlet_ffl(c1, c2, t, n, n_ambient=1.):
1✔
509
    """Front focal length of a thick singlet.
510

511
    Parameters
512
    ----------
513
    c1 : float
514
        curvature of S1
515
    c2 : float
516
        curvature of S2
517
    t : float
518
        vertex-to-vertex thickness
519
    n : float
520
        refractive index
521
    n_ambient: float
522
        refractive index of the ambient medium ("air")
523

524
    Returns
525
    -------
526
    float
527
        signed distance from S1 to the front focal point
528

529
    """
530
    phi2 = (n_ambient - n) * c2
1✔
531
    efl = singlet_efl(c1, c2, t, n, n_ambient)
1✔
532
    return -efl * (1 - t/n * phi2)
1✔
533

534

535
def twolens_efl(efl1, efl2, separation):
1✔
536
    """Use thick lens equations to compute the focal length for two elements separated by some distance.
537

538
    Parameters
539
    ----------
540
    efl1 : float
541
        EFL of the first lens
542
    efl2 : float
543
        EFL of the second lens
544

545
    separation : float
546
        separation of the two lenses
547

548
    Returns
549
    -------
550
    float
551
        focal length of the two lens system
552

553
    """
554
    phi1, phi2, t = 1 / efl1, 1 / efl2, separation
1✔
555
    phi_tot = phi1 + phi2 - t * phi1 * phi2
1✔
556
    return 1 / phi_tot
1✔
557

558

559
def twolens_power(efl1, efl2, separation):
1✔
560
    """Compute the optical power for two thin lenses in air.
561

562
    Parameters
563
    ----------
564
    efl1 : float
565
        EFL of the first lens
566
    efl2 : float
567
        EFL of the second lens
568
    separation : float
569
        separation of the two lenses
570

571
    Returns
572
    -------
573
    float
574
        optical power of the two lens system
575

576
    """
577
    return 1 / twolens_efl(efl1, efl2, separation)
1✔
578

579

580
def twolens_bfl(efl1, efl2, separation):
1✔
581
    """Use thick lens equations to compute the back focal length for two elements separated by some distance.
582

583
    Parameters
584
    ----------
585
    efl1 : float
586
        EFL of the first lens
587
    efl2 : float
588
        EFL of the second lens
589

590
    separation : float
591
        separation of the two lenses.
592

593
    Returns
594
    -------
595
    float
596
        back focal length of the two lens system.
597

598
    """
599
    phi1 = 1 / efl1
1✔
600
    numerator = 1 - separation * phi1
1✔
601
    efl = twolens_efl(efl1, efl2, separation)
1✔
602
    return numerator * efl
1✔
603

604

605
def twolens_ffl(efl1, efl2, separation):
1✔
606
    """Compute the front focal length for two thin lenses in air.
607

608
    Parameters
609
    ----------
610
    efl1 : float
611
        EFL of the first lens
612
    efl2 : float
613
        EFL of the second lens
614
    separation : float
615
        separation of the two lenses
616

617
    Returns
618
    -------
619
    float
620
        front focal length of the two lens system
621

622
    """
623
    phi2 = 1 / efl2
1✔
624
    efl = twolens_efl(efl1, efl2, separation)
1✔
625
    return -efl * (1 - separation * phi2)
1✔
626

627

628
def twolens_separation(efl1, efl2, efl):
1✔
629
    """Compute the separation required for a target two-lens EFL.
630

631
    Parameters
632
    ----------
633
    efl1 : float
634
        EFL of the first lens
635
    efl2 : float
636
        EFL of the second lens
637
    efl : float
638
        target EFL of the two-lens system
639

640
    Returns
641
    -------
642
    float
643
        separation between the two lenses
644

645
    """
646
    phi1 = 1 / efl1
1✔
647
    phi2 = 1 / efl2
1✔
648
    phi = 1 / efl
1✔
649
    return (phi1 + phi2 - phi) / (phi1 * phi2)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc