Hyperspectral reconstruction from photon-starved electron microscopy
SEM-EDS maps a meteorite section as a cube of X-ray spectra, one per pixel, and most of its voxels record zero or one photon. I reconstructed cubes from data this sparse, with the sharp electron image as a guide.
- Role
- Student researcher
- Where
- LRE, EPITA Research Laboratory
- When
- Jan to Jul 2026
- Stack
- Python, PyTorch on GPU, NumPy
- Output
- Technical report, talk at the EPITA SCIA seminar 2026
The measurement
A scanning electron microscope sweeps a focused electron beam across a polished section of the meteorite. Where the beam lands, it knocks electrons out of the inner shells of atoms, and the atoms answer with X-rays at energies that identify each element. An energy-dispersive spectrometer (EDS) counts these photons and sorts them by energy, so every beam position yields a spectrum.
The spectra of a whole scan form a hyperspectral cube, with two spatial axes and one energy axis. The chondrite cube I worked on has 1024 × 1024 pixels and 2048 energy channels: about 2.1 billion voxels, or 8.6 GB in single precision.
Most of those voxels hold zero or one photon. That is typical of EDS maps: the beam spends only a short time on each pixel, few of the electrons that hit the sample produce an X-ray that reaches the detector, and the few photons a pixel collects are spread over 2048 channels.
Two effects shape the data. Counts are whole numbers: a voxel whose expected count is 0.3 photons records 0 most of the time, sometimes 1, never 0.3. And photon arrivals are random: the count follows a Poisson distribution, whose standard deviation is the square root of its mean, so at about one photon the noise is as large as the signal. I framed the reconstruction as an inverse problem under this quantization and Poisson noise: recover the underlying cube from the counts.
Why the usual data term fails here
Many reconstruction methods minimize the sum of two terms:
minimize D(x, y) + λR(x)
Here x is a candidate cube and y the recorded counts. The data-fidelity term D measures how well x explains the counts, for example as a squared error or as the negative log-likelihood of the noise model. The regularizer R encodes what a plausible cube looks like, for example that neighboring spectra are similar. The trade-off weight λ sets the balance between them: too low and the noise passes through, too high and real detail is smoothed away. It has to be tuned, usually against a ground truth that real data does not have.
In this photon-starved regime, I showed that the classical data-fidelity term is degenerate, so I took it out of the objective.
A constraint instead of a trade-off
In its place, I put a hard box constraint on the unknowns: each value of the cube must stay between a lower and an upper bound consistent with the recorded counts. The problem becomes:
minimize R(x)
subject to l ≤ x ≤ u
The regularizer is convex and so is the box, so the whole problem is convex: any local minimum is a global one. With a single term left in the objective, there is no trade-off weight to tune.
I solved it by projected gradient. Each iteration takes a gradient step on the regularizer, which may push some values out of the box, then projects back onto the box. For a box, the projection is clipping: a value above its upper bound is set to that bound, a value below its lower bound is set to the lower bound, and the rest stay as they are.
xk+1 = clip(xk − τ∇R(xk), l, u)
The step size τ affects how fast the iterations converge, not which problem they solve. Both steps only involve each voxel and its neighbors, which suits a GPU: I ran the method in PyTorch on the real chondrite cube.
Letting the electron image guide
Along with the X-ray counts, the microscope records a backscattered-electron image of the same area. That signal is far stronger, so the image is sharp, and its gray level follows the mean atomic number of the material: mineral grains show up as regions of nearly even gray with clear edges.
I designed a bilateral regularizer guided by that image. It penalizes differences between the spectra of neighboring pixels, and weights each pair by how similar the two pixels look in the electron image. Neighbors with the same gray level, most likely in the same grain, are pulled together; neighbors across an edge are barely linked. Smoothing stays inside grains and stops at their boundaries.
Results
PSNR and the spectral angle compare a reconstruction with a known answer, so they were measured on a synthetic cube with Poisson noise. Spectral dispersion between neighboring pixels was measured on the real cube, which has no ground truth.
| Metric | Data | Result |
|---|---|---|
| PSNR against the ground truth | Synthetic, Poisson noise | +1.5 dB over raw counts |
| Spectral angle (SAM) | Synthetic, Poisson noise | 22.3° to 18.9° |
| Spectral dispersion between neighboring pixels | Real chondrite cube | 65% lower |
PSNR, the peak signal-to-noise ratio, compares the reconstruction with the ground truth through their mean squared error, on a logarithmic scale relative to the peak value: higher is better.
The spectral angle (SAM) treats each spectrum as a vector and measures its angle to the true spectrum, so it compares the shape of the spectrum regardless of its overall scale: lower is better.
Spectral dispersion between neighboring pixels measures how much the spectra of neighboring pixels differ from one another. Any smoothing lowers it, so on real data it shows how much noise was removed, while PSNR and the spectral angle on synthetic data measure accuracy.
What stays private
The code and report stay with the lab, which still uses the work. I am happy to walk through the method in an interview.