Surface flatness analysis for optical glass and precision flats measured on phase-shifting interferometers.
The module reads raw surface data straight out of the instrument (Zygo .datx
and .dat, Apre and Zygo .xyz point exports), converts each into a common
phase map in nanometres, removes the alignment terms, decomposes the residual
onto a low-order Zernike basis, and reports the standard flatness parameters
(PV, RMS, Power, Irregularity) in both nanometres and fringes.
A single class, VxManager, drives the whole pipeline so that every dataset
and every instrument is processed identically.
- Installation
- Quick start
- Package layout
- Methodology
- Configuration reference
- Instrument formats
- Dummy
- Tests
- References
Python 3.12 or newer.
python -m venv .venv
.venv/Scripts/python.exe -m pip install numpy scipy h5py pandas matplotlib openpyxl| Package | Used for |
|---|---|
numpy |
array maths, least-squares solving |
scipy |
binary morphology for aperture edge trimming |
h5py |
reading the HDF5 container inside .datx |
pandas, openpyxl |
summary sheet export (.xlsx / .csv) |
matplotlib |
surface reports |
pytest |
optional, to run the test suite |
Every instrument format is decoded in this module, so there is no third-party optics dependency.
from Manager import VxManager
# .datx carries its own calibration
mgr = VxManager(trim=13)
results = mgr.Analyse("dataset.datx", report_dir="report")
for result in results:
print(result.summary())
mgr.ExportSummary(results, "summary.xlsx")# .xyz carries no calibration, so it is supplied through the config
mgr = VxManager(
pixel_size_mm=0.07310224,
wavelength_nm=633.0,
waves_per_fringe=0.5,
trim=13,
)
result = mgr.Analyse("2025-10-01_18-09-08_top.xyz")[0]
print(result.pv_fr, result.rms_fr, result.power_fr, result.irregularity_fr)Batch a whole folder, rewriting the summary after every file so an interrupted run keeps its completed rows:
mgr.AnalyseFolder("datx", pattern="*.datx",
report_dir="report", summary_path="summary.xlsx")The files in test/ are the worked example for each instrument as well as the
test suite. Each one states that instrument's calibration as constants, runs
the full pipeline on the matching dummy dataset, and can be run directly:
python test/test_zygo_datx.py # writes reports and a summary sheet
python test/test_apre_xyz.py # shows the surface report
python test/test_development_xyz.py # top/bottom pair as surfaces 1 and 2To analyse production data, point the *_DIR constant at the real folder or
call VxManager directly as shown above.
Manager/
__init__.py public API: VxManager, AnalysisConfig, Measurement, SurfaceResult
manager.py VxManager, the single orchestrator
Models/ AnalysisConfig, Measurement, SurfaceResult
Load/ extraction, one module per format
datx.py Zygo .datx (HDF5), ZygoExtract
xyz.py Apre / Zygo .xyz point exports, parsed in parallel
dat.py MetroPro binary .dat interferograms
common.py dataset naming, top/bottom pairing
PreProcess/ coordinates, masking, edge trim, piston / tip-tilt / power removal
Process/ PV, RMS, Power, Irregularity, modal fit
Polynomials/ Zernike and Jacobi polynomials, least-squares mode fitting
Plot/ surface report figure, summary sheet export
VxManager exposes the pipeline as five steps:
| Method | Role |
|---|---|
Load(path) |
dispatches on file extension to _extractZygo, _extractApre or _extractXYZ; returns one Measurement per surface |
Preprocess(m) |
edge trim, then piston and tip/tilt removal, optional crop |
Measure(m) |
modal fit and the four flatness metrics |
Analyse(path) |
the three above, end to end, optionally writing a report |
AnalyseFolder(dir) |
Analyse over every matching file |
Report, ExportSummary |
figure and summary sheet output |
Adding a format means adding one entry to VxManager._EXTRACTORS and one
_extract* method.
Interferometers report optical path difference in fringes. A .datx file
stores the scale factors alongside the data, and each surface of a stacked
assembly is converted with its own calibration:
where
A .dat file is a big-endian MetroPro binary: a fixed header holding the
camera regions and the calibration, then the intensity frames, then the phase
map as 32-bit integer counts. Counts are decoded with the same relation,
divided by the phase resolution the instrument digitised at,
for 12, 15 and 17 bit resolution respectively, with counts at or above
2147483640 marking unmeasured pixels. Field offsets follow the MetroPro binary
file format reference (Zygo Corporation, n.d.); the header also supplies the
wavelength, the scale factor and the lateral resolution, so a .dat needs no
configuration either.
.xyz exports carry no header calibration, so pixel size, wavelength and
waves per fringe come from AnalysisConfig, and z_scale converts the file's
own height unit into nanometres (Apre exports micrometres, so z_scale=1e3).
Point exports are parsed across worker processes and scattered onto a grid padded so that the measured aperture stays centred on the grid centre, which is where the Zernike pupil origin is placed in step 4.
Invalid pixels ("No Data" dropouts, and everything outside the aperture) are
held as NaN and excluded from every fit and statistic.
Edge pixels of a phase map are unreliable: the detector footprint straddles the physical edge of the part, so those pixels mix surface and background and bias PV strongly. The outer 13 pixel layers are therefore removed by iterated binary erosion,
applied 13 times with the 4-connected structuring element NaN islands do not grow. This is standard binary morphology
(Serra, 1982; Haralick, Sternberg, & Zhuang, 1987).
Piston (a constant offset) and tip/tilt (a rigid rotation of the part in the cavity) are artefacts of alignment, not of the surface, and are removed before any metric is computed:
with
All fits in the module use the same routine, Polynomials.lstsq, which solves
the normal equations through the singular value decomposition provided by
numpy.linalg.lstsq, giving the minimum-norm solution even when the design
matrix is rank deficient (Golub & Van Loan, 2013).
The conditioned surface is projected onto the first four Zernike polynomials
in Fringe (University of Arizona) ordering,
In polar coordinates
The factorial form above is implemented directly in
Polynomials.zernike_radial, but it is numerically poor at high order and
recomputes shared work for every mode. The sequence generator used by the
pipeline instead exploits the identity between the Zernike radial polynomials
and the Jacobi polynomials
and evaluates the Jacobi polynomials by their three-term recurrence
with the coefficients of Abramowitz and Stegun (1964, ch. 22) and the NIST
Digital Library of Mathematical Functions (Olver et al., n.d., §18.9). For a
sequence of modes this evaluates one Jacobi recurrence per
Modes are generated unnormalised for the metric basis, so each has zero-to-peak amplitude 1 and the fitted coefficients read directly in nanometres of surface. The orthonormal scaling
is available through norm=True when unit-RMS modes are wanted instead
(Noll, 1976).
The coefficient vector is obtained by least squares over the valid pixels only:
| Metric | Definition | Basis |
|---|---|---|
| PV | peak-to-valley of the surface | |
| RMS | areal |
|
| Power | peak-to-valley of the best-fit sphere | |
| Irregularity |
|
PV of the residual after |
Power deserves a note. With unnormalised modes the defocus term is
Irregularity separates the two failure modes that PV alone conflates. A part may be out of tolerance because it is spherically bowed (power, often a polishing or mounting effect and sometimes correctable) or because it is locally rough and astigmatic (irregularity, which is not). Subtracting the fitted piston, tilt and defocus leaves exactly the second component, following the wavefront deformation tolerances of ISO 10110-14 (Malacara, 2007).
Every metric is also reported in fringes, so that results are comparable across instruments and wavelengths:
With
AnalysisConfig, also settable as keyword arguments to VxManager:
| Field | Default | Meaning |
|---|---|---|
pixel_size_mm |
None |
lateral sampling; required for .xyz, read from file for .datx
|
wavelength_nm |
633.0 |
measurement wavelength; read from file for .datx
|
waves_per_fringe |
0.5 |
interferometric scale factor |
z_scale |
1.0 |
factor converting the file's height unit to nm (1e3 for micrometres) |
swap_xy |
False |
set when an .xyz export lists row before column |
trim |
13 |
aperture edge layers removed; 0 disables |
crop |
False |
crop to the valid bounding box after form removal |
n_workers |
8 |
processes used to parse .xyz files |
| Extension | Extractor | Container | Calibration | Surfaces per file |
|---|---|---|---|---|
.datx |
_extractZygo |
HDF5 | from the file | one per stacked assembly entry |
.dat |
_extractApre |
MetroPro binary | from the file | one |
.xyz |
_extractXYZ |
text point list | from the config | one |
dummy dataset/ holds three small synthetic datasets per instrument format,
so the pipeline can be run without production data.
| Folder | Datasets | Files | Type |
|---|---|---|---|
apre/ |
3 | 6 | S1 to S3, each as a dense .xyz in micrometres and as a MetroPro .dat |
zygo/ |
3 | 3 | Zygo .datx, a stacked assembly of 2 surfaces each, so 6 surfaces in total |
development xyz/ |
3 | 6 | development-set .xyz, one top and one bottom export per dataset |
Every surface is a circular aperture on a 96 x 80 grid. The development-set exports are partial and start at index 6, so they pad to 108 x 92 when loaded, which is what exercises the centred-padding path. Regenerate with:
python "dummy dataset/generate.py"The surfaces are analytic (tilt, defocus, astigmatism, ripple and noise), so the metrics are repeatable but not physically meaningful: they exist to exercise the readers and the pipeline, not to validate against instrument results.
.venv/Scripts/python.exe -m pytest test -qOne module per source format, each trimming the same 13 pixel layers, and each also runnable on its own as the worked example for that instrument:
| File | Covers |
|---|---|
test/test_zygo_datx.py |
calibration read from file, stacked surfaces, trim depth, piston and tilt removal, report and summary output, folder batching |
test/test_apre_xyz.py |
config-supplied calibration, pixel spacing, micrometre to nanometre scaling, dense grid with No Data, crop path |
test/test_development_xyz.py |
partial export padded to keep the aperture centred, top/bottom pairing, summary column layout, .xlsx and .csv agreement |
Each file declares its instrument's wavelength, waves per fringe and pixel size as constants, so the calibration used for a given source is stated in one place and asserted on every run.
Abramowitz, M., & Stegun, I. A. (Eds.). (1964). Handbook of mathematical functions with formulas, graphs, and mathematical tables (Applied Mathematics Series 55). National Bureau of Standards.
Born, M., & Wolf, E. (1999). Principles of optics: Electromagnetic theory of propagation, interference and diffraction of light (7th ed.). Cambridge University Press.
Creath, K. (1988). Phase-measurement interferometry techniques. In E. Wolf (Ed.), Progress in optics (Vol. 26, pp. 349-393). Elsevier.
Golub, G. H., & Van Loan, C. F. (2013). Matrix computations (4th ed.). Johns Hopkins University Press.
Haralick, R. M., Sternberg, S. R., & Zhuang, X. (1987). Image analysis using mathematical morphology. IEEE Transactions on Pattern Analysis and Machine Intelligence, PAMI-9(4), 532-550.
International Organization for Standardization. (2003). Optics and photonics: Preparation of drawings for optical elements and systems - Part 14: Wavefront deformation tolerance (ISO 10110-14:2003).
International Organization for Standardization. (2012). Geometrical product specifications (GPS): Surface texture: Areal - Part 2: Terms, definitions and surface texture parameters (ISO 25178-2:2012).
International Organization for Standardization. (2015). Optics and photonics: Preparation of drawings for optical elements and systems - Part 5: Surface form tolerances (ISO 10110-5:2015).
Malacara, D. (Ed.). (2007). Optical shop testing (3rd ed.). John Wiley & Sons.
Noll, R. J. (1976). Zernike polynomials and atmospheric turbulence. Journal of the Optical Society of America, 66(3), 207-211.
Olver, F. W. J., Olde Daalhuis, A. B., Lozier, D. W., Schneider, B. I., Boisvert, R. F., Clark, C. W., Miller, B. R., Saunders, B. V., Cohl, H. S., & McClain, M. A. (Eds.). (n.d.). NIST digital library of mathematical functions. Retrieved from https://dlmf.nist.gov/18.9
Prata, A., & Rusch, W. V. T. (1989). Algorithm for computation of Zernike polynomials expansion coefficients. Applied Optics, 28(4), 749-754.
Serra, J. (1982). Image analysis and mathematical morphology. Academic Press.
Shakibaei, B. H., & Paramesran, R. (2013). Recursive formula to compute Zernike radial polynomials. Optics Letters, 38(14), 2487-2489.
Wyant, J. C., & Creath, K. (1992). Basic wavefront aberration theory for optical metrology. In R. R. Shannon & J. C. Wyant (Eds.), Applied optics and optical engineering (Vol. 11, pp. 1-53). Academic Press.
Zernike, F. (1934). Beugungstheorie des Schneidenverfahrens und seiner verbesserten Form, der Phasenkontrastmethode. Physica, 1(7-12), 689-704.
Zygo Corporation. (n.d.). MetroPro reference guide: Binary data file formats [Software documentation].
Harris, C. R., Millman, K. J., van der Walt, S. J., Gommers, R., Virtanen, P., Cournapeau, D., Wieser, E., Taylor, J., Berg, S., Smith, N. J., Kern, R., Picus, M., Hoyer, S., van Kerkwijk, M. H., Brett, M., Haldane, A., del Rio, J. F., Wiebe, M., Peterson, P., ... Oliphant, T. E. (2020). Array programming with NumPy. Nature, 585(7825), 357-362. https://doi.org/10.1038/s41586-020-2649-2
Hunter, J. D. (2007). Matplotlib: A 2D graphics environment. Computing in Science & Engineering, 9(3), 90-95. https://doi.org/10.1109/MCSE.2007.55
Virtanen, P., Gommers, R., Oliphant, T. E., Haberland, M., Reddy, T., Cournapeau, D., Burovski, E., Peterson, P., Weckesser, W., Bright, J., van der Walt, S. J., Brett, M., Wilson, J., Millman, K. J., Mayorov, N., Nelson, A. R. J., Jones, E., Kern, R., Larson, E., ... SciPy 1.0 Contributors. (2020). SciPy 1.0: Fundamental algorithms for scientific computing in Python. Nature Methods, 17(3), 261-272. https://doi.org/10.1038/s41592-019-0686-2
The ISO editions cited are those referenced during development. Confirm the currently published edition before quoting a tolerance in a drawing or an inspection report.