All code for the following implementations can be found at: https://github.com/ageppert01/single_photon_challenge/tree/main
The goal of Stage 1 is to turn the input SPAD burst into a single intermediate image that keeps as much useful signal as possible while reducing noise and motion blur. A naive sum is a natural starting point because summing more frames increases the photon count. However, in dynamic scenes, the camera and objects move across the burst, so directly summing many frames can smear object boundaries and produce significant blur.
The naive-sum baselines exposed the main trade-off. NaiveSum16 was a useful candidate for a Stage 2 input because it preserved sharper structure and had relatively little motion blur, but it discarded most of the available photons. NaiveSum256 recovered much more scene information and had the best quantitative performance among the naive-sum baselines reported for the challenge, so we used it as the main Stage 1 comparison point. MCA was designed to use the larger 256-frame window while reducing the blur caused by summing those frames without alignment.
As an initial experiment, we tried aligning individual frames using translation only, with the last frame as the reference. In practice, this performed poorly because individual binary SPAD frames contain very little visible signal. The estimated shifts were unstable and often reflected photon noise rather than actual camera motion.
To make alignment more reliable, we moved to a blockwise translation method. Instead of estimating motion from single frames, we grouped nearby frames into small non-overlapping blocks and summed each block before alignment. In our experiments, we used 8-frame blocks within a 256-frame window. These block sums provided a higher-signal representation for estimating motion, while still keeping the motion within each block relatively small. We then estimated a translation from each block to the final reference block and applied that same translation to all frames in the block before aggregation.
The adaptive MCA method builds on this blockwise approach by adding global scale estimation and local refinement. Translation can shift a block, but it cannot account for changes in apparent scale. Some scenes contain zoom-like motion, where objects appear slightly larger or smaller over time. To handle this, we tested several small zoom-in and zoom-out adjustments for each block, and estimated the translation that best aligned the adjusted block to the reference. We then kept the scale-and-translation pair with the best alignment. We also used an overlap check while walking backward through the blocks, stopping once the valid overlap with the reference became too small.
In the adaptive version, we also applied dense TV-L1 optical flow after the global scale-and-translation alignment. The global transform captures the dominant motion of the block, while optical flow accounts for smaller residual motion that varies across the image. The motion is estimated on the block sum, but the resulting warp is applied to each individual frame in that block. During accumulation, we keep a validity mask and normalize by the number of valid contributions at each pixel, so pixels outside the aligned overlap region do not incorrectly affect the final average.
Overall, MCA produces a Stage 1 reconstruction that uses substantially more frames than NaiveSum16 while avoiding much of the blur introduced by NaiveSum256. The adaptive version is more robust than translation-only blockwise alignment in scenes with zoom or mixed motion, although this comes with higher computational cost from testing multiple zoom adjustments and applying dense optical-flow refinement.
A convolutional LSTM walks through all 1,024 frames one at a time, maintaining a spatial hidden state that accumulates photon statistics, edges, and coarse motion.
A small decoder head maps the final hidden state back to an RGB image, supervised with an L1 loss against ground truth. The model learns how to integrate photons over time instead of using one fixed averaging rule.
Single-photon bursts are naturally ordered in time: each of the 1,024 frames is a noisy binary observation of the scene. Recurrent neural networks (RNNs) are a standard way to summarise such sequences into a fixed-size state that depends on the entire history, without committing to a fixed hand-crafted fusion rule (e.g. a simple temporal average).
For this work we use a convolutional LSTM (ConvLSTM) as the temporal backbone. At each time step the cell receives a downsampled RGB-like frame (binary values in [0, 1] after unpacking and pooling) and updates a spatial hidden state and cell state over the image grid. Convolutional recurrence preserves spatial structure. Gates and updates use convolutions over H × W, so local neighbourhoods evolve coherently across time. The full burst is processed in chunks (e.g. 64 frames) to keep memory manageable. The hidden and cell states are carried across chunks so the representation stays consistent over all 1,024 steps.
After the last frame, the final hidden state is treated as a high-dimensional feature map encoding a learned summary of the burst (photon statistics, edges, coarse motion, etc.). A small convolutional decoder head maps this map to three channels in [0, 1], yielding an image at the RNN resolution. That image is supervised with an ℓ1 loss to the ground truth RGB (downsampled to match during training), so the recurrent trunk and decoder are trained end-to-end for reconstruction. At evaluation, the decoder output is upsampled to 800×800 to match the challenge resolution and to compare against baselines and metrics (PSNR, MS-SSIM, LPIPS).
Compared to a naive sum baseline (averaging binary frames), the ConvLSTM can in principle learn how to integrate photons over time rather than applying a single global average. That matters when the burst contains motion or varying overlap between frames.
Gradient checkpointing is used over chunk boundaries to limit GPU memory while keeping the effective sequence length at 1,024 steps. The resulting model serves as Stage 1 in a two-stage pipeline. It produces a structured intermediate image (or feature map) for a later generative refinement stage.
The diffusion stage takes the MCA output and produces a perceptually enhanced reconstruction. MCA does an excellent job at recovering structure and reducing photon noise, but the averaging it relies on inevitably smooths over high-frequency texture. Our goal with diffusion is to commit to plausible texture rather than averaging across it. Generative image priors are good choice for this task.
We use a Palette-style conditional latent diffusion model built on top of Stable Diffusion 2.1 with zero-terminal SNR and v-prediction. The MCA output is encoded into a 4-channel latent representation by gQIR's quanta-aligned VAE, a recently published encoder trained specifically on sparse single-photon data. This latent is concatenated channel-wise with a noisy latent and passed into the diffusion UNet. The UNet's input convolution was expanded from 4 to 8 channels (zero-initialized on the new half so training begins from the pretrained behavior), and 20 DDIM steps of reverse diffusion produce the final latent. The standard SD 2.1 VAE then decodes the result back to an 800×800 RGB image. Regarding text-conditioning, null embedding is passed throughout, since the MCA output already carries all the information the model needs.
A key design decision was using a dual-VAE setup. Namely, gQIR's quanta-aligned VAE for encoding the noisy MCA input, and the standard SD VAE for decoding the final output. The two image distributions are different enough that a single encoder trained on natural images alone could not reliably interpret the sparse-photon data.
Another important decision was how to adapt the pretrained UNet. We initially tried LoRA on the attention layers, which is the standard parameter-efficient fine-tuning approach, but the resulting model hallucinated completely. We resolved this by freezing only the encoder and unfreezing the entire decoder and middle block, giving roughly 612M trainable parameters out of roughly 886M total. This approach succeeded where LoRA had failed.
Training was performed on 1,665 paired MCA-output / ground-truth images from the competition dataset, with 185 held out for validation. We used distributed training across 4 GPUs with mixed precision and gradient checkpointing, optimizing v-prediction MSE loss in latent space. At inference time, 20 DDIM steps was found empirically to outperform both shorter and longer schedules (sweeps from 10 to 1000 steps showed that additional denoising past 20 steps degrades all three evaluation metrics simultaneously, likely because extra steps reintroduce hallucinated detail).
The challenge is evaluated not only by pixel-level accuracy, but also by perceptual quality. A model trained only with L1 or L2 loss may achieve reasonable PSNR, but often produces overly smooth outputs. GANs help address this by adding an adversarial objective: the generator must produce images that are not only close to the ground truth, but also locally realistic. This is especially useful for metrics like MS-SSIM and LPIPS, which reward structural and perceptual similarity.
To improve perceptual quality, we implemented a Pix2Pix-style conditional GAN for paired image-to-image translation. Unlike a standard GAN that generates images from random noise, our model is conditioned directly on a degraded input image. In our case, the input is a naive-sum reconstruction created by summing a subset of binary SPAD frames, and the target is the corresponding clean RGB ground truth. This turns the problem into a learned mapping of noisy naive-sum image → clean reconstruction.
Our generator is a U-Net, which takes the noisy input image and predicts a denoised reconstruction. Skip connections help preserve spatial structure from the input while allowing the network to refine missing details.
The discriminator is a PatchGAN, which does not assign one global real/fake score. Instead, it evaluates the image patch-by-patch, producing a classification matrix. This gives the generator localized feedback about texture, edges, and fine structure.
During training, each input is generated by randomly selecting a frame budget: B ∈ {16, 32, 64, 128, 256, 512, 1024}
The model is trained on naive-sum images created from the last B photon frames. This forces the generator to handle a wide range of degradation levels, from extremely noisy low-budget inputs to cleaner high-budget inputs. The generator is trained with two objectives:
Adversarial loss: fool the discriminator into classifying generated patches as real
Pixel reconstruction loss: stay close to the ground-truth image
The discriminator is trained to correctly classify real ground-truth patches as real and generated patches as fake.
At inference time, only the trained generator is used. The discriminator is discarded, and the noisy test input is passed through the generator to produce the final denoised image.