Retro-Engineering Zernike Polynomials for Computer Vision


Definition of Zernike Polynomials

Zernike polynomials are a set of orthogonal functions defined over the unit disk, typically expressed in polar coordinates (ρ, θ). They form a complete basis for representing smooth, continuous functions on a circular aperture, making them ideal for modeling optical wavefronts. Each polynomial is indexed by radial degree n and azimuthal frequency m, with forms like piston (n=0, m=0), tilt, defocus, astigmatism, coma, and higher modes. Their orthogonality ensures efficient decomposition without redundancy, which is why they’re a staple in wavefront analysis.

They are useful for modeling complex aberration in the light path in the eye. Let’s summarize them by distinguishing 2 main types : Low Order Aberrations and High Order Aberrations

Example image

What Are High-Order Aberrations? Aberrations are classified into low-order (LOAs) and high-order (HOAs). LOAs include common refractive errors like myopia (nearsightedness), hyperopia (farsightedness), and astigmatism, which stem from basic shape issues in the eye and can typically be corrected with glasses, contacts, or standard surgery. In contrast, HOAs are more subtle and complex distortions, such as coma, trefoil, or spherical aberration, resulting from minute corneal irregularities, scars, or trauma. These cannot be fully addressed by traditional glasses and often lead to symptoms like glare, halos, or reduced contrast sensitivity, especially in low light.

Example image

How High-Order Aberrations Affect Light Entry in Diseases Like Keratoconus In conditions like keratoconus, the cornea thins and bulges into a cone shape, severely disrupting light entry. This causes pronounced HOAs, where light rays don’t converge properly on the retina, resulting in blurred, distorted vision. Patients experience halos around lights, glare, light sensitivity, night vision problems, and even headaches or eye strain. Daily life is profoundly impacted: driving becomes hazardous due to poor night vision, reading or using screens causes fatigue. Simple tasks like recognizing faces or navigating spaces turn challenging, affecting work, social interactions, and independence.

Example image

Transforming Raw Data from the MS39 Device into Zernike Polynomials The MS39 is a state-of-the-art corneal topographer and tomographer from CSO, combining Placido disk topography for surface mapping with spectral-domain optical coherence tomography (SD-OCT) for cross-sectional imaging. It provides detailed data on pachymetry (thickness), elevation, curvature, and dioptric power across anterior, posterior, and stromal surfaces. My pipeline processes raw CSV exports from the MS39, extracting diameter zones (2-7mm), fitting a best-fit sphere for centering, computing Zernike coefficients up to order 6, applying optical path difference corrections, and generating visualizations. This transforms noisy, polar-coordinate data into standardized, physiologically meaningful polynomials. Goal: Understanding and Predicting Complex High-Order Aberrations with Computer Vision The ultimate aim is to leverage computer vision (CV) techniques to not just describe but predict HOAs from MS39 data. By extracting Zernike coefficients as features, we can feed them into ML models to forecast disease progression, classify conditions like keratoconus, or optimize custom treatments. This pipeline serves as a feature engineering foundation, enabling CV algorithms to analyze topography maps for patterns invisible to the naked eye. Methods for Zernike Pipeline in MS39 Data Processing This section outlines the core methods of a robust pipeline for processing MS39 corneal topography data into Zernike polynomials, designed to generate ML-ready features for predicting high-order aberrations (HOAs). Each method is critical for transforming raw data into physiologically meaningful coefficients, with concise code snippets provided.

MS39 Data Processing Pipeline

1. Data Ingestion and Preprocessing

Purpose: Extracts elevation data (anterior, posterior, stromal surfaces) from MS39 CSV files across 2-7mm diameter zones, handling noise and missing values.
Why It Matters: Creates a structured dataset for ML feature extraction, demonstrating robust data handling for medical imaging. This part of the code is tailored for MS39 exports, which are polar coordinate data.

def process_single_csv_zones(p):
    r = {'filename': os.path.basename(p), 'surfaces': {}}
    for sn, sr, nr, mn, mx in segments:
        ss, m = sn.replace('elevation_', ''), lire_segment(p, sr, nr)
        if m.empty: continue
        m = m.mask((m < mn) | (m > mx)).replace(-1000, np.nan)
        r['surfaces'][ss] = {}
        for dia in [2, 3, 4, 5, 6, 7]:
            zd = extract_diameter_zones(m, dia)
            if zd is not None: r['surfaces'][ss][f'{dia}mm'] = {'data': zd}
    return r

2. Best Fit Sphere (BFS) Centering

Purpose: Fits a sphere to elevation data using least-squares optimization, centers coordinates on the sphere’s vertex, and computes residual maps. Why It Matters: This is a standard step in corneal topography analysis, ensuring that the residual maps are centered on the sphere’s vertex. Without it, the maps would only showcase standard light aberrations (like coma and spherical aberration) without accounting for the geometry of the cornea. With it, we can isolate the aberrations needed for clinical diagnosis.

def calculate_bfs(zd,rs=0.2):
    x,y,z=polar_to_cartesian_coords(zd,rs)
    if len(x)<4:return None,None
    zr=np.max(z)-np.min(z)
    er=np.clip((np.max(np.sqrt(x**2+y**2))**2)/(2*zr),6.,15.) if zr>1e-3 else 8.
    ig=[0.,0.,np.max(z)+er,er]
    res=least_squares(bfs_residuals,ig,args=(x,y,z),method='lm',max_nfev=1000)
    if not res.success or not (abs(res.x[0])<5 and abs(res.x[1])<5 and 4.<res.x[3]<20.):return None,None
    return {'center_x':res.x[0],'center_y':res.x[1],'center_z':res.x[2],'radius':res.x[3],'rms_residual':np.sqrt(np.mean(res.fun**2)) },{'n_points':len(x),'convergence':res.success}

3. Zernike Coefficient Fitting

Purpose: Fits Zernike polynomials (up to order 6) to residual maps, producing coefficients for aberrations like coma and spherical aberration. Why It Matters: Generates compact, interpretable features for ML tasks like disease classification or progression prediction.

def fit_zernike_coefficients(x,y,z,mo,pr):
    vm=np.isfinite(x)&np.isfinite(y)&np.isfinite(z)
    xv,yv,zv=x[vm],y[vm],z[vm]
    if len(xv)<10:return None,None,None
    rho,theta=np.sqrt(xv**2+yv**2)/pr,np.arctan2(yv,xv)
    i=rho<=1.001
    rho,theta,zv=rho[i],theta[i],zv[i]
    if len(rho)<10:return None,None,None
    modes=generate_zernike_modes(mo)
    A=np.vstack([zernike_polynomial(n,m,rho,theta) for j,n,m,name in modes]).T
    c,_,_,_=np.linalg.lstsq(A,zv,rcond=None)
    if not np.all(np.isfinite(c)):return None,None,None
    zf=A@c
    ve=100*(1-np.sum((zv-zf)**2)/np.sum((zv-np.mean(zv))**2)) if np.var(zv)>1e-12 else 100.
    return c,modes,{'rms_residual':np.sqrt(np.mean((zv-zf)**2)),'n_points_fitted':len(rho),'variance_explained_pct':ve}

4. Optical Path Difference (OPD) Correction

Purpose: Converts geometric coefficients to OPD units using a correction factor (-0.376) and zeros the piston term. Why It Matters: This reproduces the industry standard where the (0,0) is NOT at the center of the cornea but at the Apex (most elevated point) of the cornea

def apply_opd_and_convention_correction(coefficients_mm, modes):
    correction_factor = -0.376
    corrected_coefficients_mm = np.array(coefficients_mm) * correction_factor
    if len(corrected_coefficients_mm) > 0:
        corrected_coefficients_mm[0] = 0.0
    return corrected_coefficients_mm

5. Visualization and Output

Purpose: Saves Zernike coefficients to CSV and generates aberration maps for validation. Why It Matters: Ensures interpretability and trust in ML pipelines, crucial for healthcare applications.

def save_results_to_csv(ar, of):
    sf = {k: v for k, v in ar['files'].items() if v.get('status') == 'success'}
    if not sf: return
    all_geom_results, all_opd_results = [], []
    rm = generate_zernike_modes()
    ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    for fn, fd in sf.items():
        geom_file_row = {'filename': fn}
        opd_file_row = {'filename': fn}
        for sn, sd in fd.get('zernike_results', {}).get('surfaces', {}).items():
            for zn, zd in sd.items():
                p = f"{sn}_{zn}"
                c_geom = zd.get('coefficients_geometric_mm')
                c_opd = zd.get('coefficients_opd_corrected_mm')
                for j, n, m, name in rm:
                    geom_file_row[f'{p}_zernike_z{j}_{name}_um'] = c_geom[j] * 1000 if c_geom is not None and j < len(c_geom) else np.nan
                    opd_file_row[f'{p}_zernike_z{j}_{name}_um'] = c_opd[j] * 1000 if c_opd is not None and j < len(c_opd) else np.nan
        all_geom_results.append(geom_file_row)
        all_opd_results.append(opd_file_row)
    df_geom = pd.DataFrame(all_geom_results)
    df_opd = pd.DataFrame(all_opd_results)
    df_geom.to_csv(os.path.join(of, f"batch_results_geom_{ts}.csv"), index=False, sep=';', float_format='%.6f')
    df_opd.to_csv(os.path.join(of, f"batch_results_opd_{ts}.csv"), index=False, sep=';', float_format='%.6f')

Relevance to ML: This pipeline transforms complex MS39 data into Zernike coefficients, ideal features for ML tasks like classifying keratoconus or predicting HOA progression. It showcases skills in data preprocessing, optimization, and feature engineering, directly applicable to Machine Learning Engineering.