1+ # zernikeTest.py
2+
3+ import numpy as np
4+ from scipy .special import gamma
5+
6+ def _zrf (n , m , r ):
7+ R = np .zeros_like (r , dtype = float )
8+ # n-m must be even and non-negative
9+ if (n - m ) % 2 != 0 or n < m :
10+ return R
11+
12+ for s in range (int ((n - m ) / 2 ) + 1 ):
13+ num = (- 1 )** s * gamma (n - s + 1 )
14+ denom = (gamma (s + 1 ) * gamma ((n + m ) / 2 - s + 1 ) * gamma ((n - m ) / 2 - s + 1 ))
15+ R += num / denom * r ** (n - 2 * s )
16+ return R
17+
18+ def noll_to_nm (j ):
19+ """Corrected Noll to (n, m) mapping."""
20+ n = 0
21+ j1 = j
22+ while j1 > (n + 1 ):
23+ j1 -= (n + 1 )
24+ n += 1
25+
26+ # The number of modes up to radial degree n-1 is n(n+1)/2
27+ # But Noll uses a specific pyramid. Let's use the explicit Noll formula:
28+ n = int ((np .sqrt (8 * j - 7 ) - 1 ) / 2 )
29+ m_abs = j - n * (n + 1 ) // 2 - 1
30+
31+ # Correcting parity and m value
32+ if n % 2 == 0 :
33+ m = 2 * int (m_abs / 2 )
34+ else :
35+ m = 2 * int ((m_abs + 1 ) / 2 ) - 1
36+
37+ if j % 2 > 0 :
38+ m = - m
39+ return n , m
40+
41+ def zernike (j , r , theta ):
42+ n , m_val = noll_to_nm (j )
43+ m = abs (m_val )
44+ # Use a small epsilon for the boundary to avoid floating point clipping
45+ r_safe = np .where (r <= 1.000001 , r , 0.0 )
46+
47+ if m == 0 :
48+ return np .sqrt (n + 1 ) * _zrf (n , 0 , r_safe )
49+ elif j % 2 == 0 :
50+ return np .sqrt (2 * (n + 1 )) * _zrf (n , m , r_safe ) * np .cos (m * theta )
51+ else :
52+ return np .sqrt (2 * (n + 1 )) * _zrf (n , m , r_safe ) * np .sin (m * theta )
53+
54+ # --- RE-RUN VALIDATION ---
55+ print ("--- Revised Zernike Validation ---" )
56+ n4 , m4 = noll_to_nm (4 )
57+ print (f"j=4 maps to n={ n4 } , m={ m4 } " )
58+ val_4 = zernike (4 , np .array ([1.0 ]), np .array ([0.0 ]))[0 ]
59+ print (f"j=4 (Defocus) at r=1.0: { val_4 :.5f} (Target: 1.73205)" )
60+
61+ n100 , m100 = noll_to_nm (100 )
62+ print (f"j=100 maps to n={ n100 } , m={ m100 } " )
63+ val_100 = zernike (100 , np .array ([1.0 ]), np .array ([0.0 ]))[0 ]
64+ print (f"j=100 at r=1.0: { val_100 :.5f} (Target: ~5.099)" )
0 commit comments