DiffractionBlurGenerator#
- class deepinv.physics.generator.DiffractionBlurGenerator(psf_size, zernike_index=tuple(range(4, 12)), fc=0.2, max_zernike_amplitude=0.15, zernike_perturbation_amplitude=0.0, pupil_size=(256, 256), apodize=False, random_rotate=False, index_convention='noll', device='cpu', dtype=torch.float32, rng=None)[source]#
Bases:
PSFGeneratorDiffraction limited blur generator.
Generates 2D diffraction PSFs in optics using Zernike decomposition of the phase mask (Fresnel/Fraunhoffer diffraction theory, Fourier optics).
Zernike polynomials are a sequence of orthogonal polynomials defined on the unit disk. They are commonly used in optical systems to describe wavefront aberrations.
The PSF is modeled as:
\[h(\cdot; \lambda) = \left| \mathcal{F} \left[ \mathbb{1}_{|\boldsymbol{\rho}| \leq 1} \cdot \exp \left( - i 2 \pi \sum_k \frac{a_k}{\lambda} z_k(\boldsymbol{\rho}) \right) \right](\cdot) \right|^2\]where \(\boldsymbol{\rho}\) are normalized pupil-plane coordinates (on the unit disk), \(a_k\) are the Zernike coefficients in physical units (nm OPD, wavelength-independent), \(\lambda\) is the emission wavelength (nm), and \(z_k\) are the Zernike polynomials.
The phase in waves is therefore \(\theta_k(\lambda) = a_k / \lambda\), so the same physical aberration produces a stronger wavefront error (in waves) at shorter wavelengths.
For multi-channel (multi-colour) imaging the generator supports a perturbation model:
\[\theta_k^{(b,c)} = \underbrace{\theta_k^{(b)} \cdot \frac{\lambda_{\text{ref}}}{\lambda_c}}_{\text{monochromatic, rescaled}} + \underbrace{\Delta\theta_k^{(b,c)}}_{\text{chromatic perturbation}}\]where \(\theta_k^{(b)}\) are base coefficients (in waves at \(\lambda_{\text{ref}}\), i.e. channel 0) shared across channels, and \(\Delta\theta_k^{(b,c)}\) are small per-channel perturbations (e.g. sample-induced dispersion). The cutoff frequency is also wavelength-dependent:
\[f_c^{(c)} = \frac{\mathrm{NA} \cdot p}{\lambda_c}\]where \(\mathrm{NA}\) is the numerical aperture and \(p\) is the pixel size.
See Lakshminarayanan and Fleck[1] or this link or
deepinv.physics.generator.Zernikefor more details.In the ideal diffraction-limited case (i.e., no aberrations), the PSF corresponds to the Airy pattern.
The Zernike polynomials \(z_k\) are indexed using the
'noll'or'ansi'convention (defined byindex_conventionparameter). Conversion from the two conventions to the standard radial-angular indexing is done internally (see wikipedia page).- Parameters:
psf_size (tuple) – the shape
H x Wof the generated PSF in 2Dzernike_index (tuple[int, ...], tuple[tuple[int, int], ...]) –
activated Zernike coefficients in the following
index_conventionconvention. It can be either:a tuple of
intcorresponding to the Noll or ANSI indices, in which case theindex_conventionparameter is required to interpret them correctly.a tuple of
tuple[int, int]corresponding to the standard radial-angular indexing \((n,m)\). In this case, theindex_conventionparameter is ignored.
Defaults to
(4, 5, 6, 7, 8, 9, 10, 11), correspond to radial ordernfrom 2 to 3 (included) and the spherical aberration. These correspond to the following aberrations: defocus, astigmatism, coma, trefoil and spherical aberration.fc (float, tuple[float, ...], list[float], torch.Tensor) –
default cutoff frequency
(NA/emission_wavelength) * pixel_size. Should be in[0, 0.25]to respect the Shannon-Nyquist sampling theorem, defaults to0.2.At construction time, only a scalar
floator a 1D tensor/sequence of lengthCare accepted. A 2D tensor raises aValueError.At step time (passed to
step()),fcmay additionally be a 2D tensor of shape(B, C)for full per-(batch, channel) control. The output PSF shape is then driven byfcas follows:float/ scalar:(batch_size, 1, H, W).(C,)1D tensor/sequence:(batch_size, C, H, W).(B, C)2D tensor:(B, C, H, W).(1, C)2D tensor:(batch_size, C, H, W).(B, 1)2D tensor:(B, 1, H, W).
max_zernike_amplitude (float) – default amplitude of the base Zernike coefficients (in waves at the channel-0/reference cutoff frequency), defaults to
0.15. The amplitude of each coefficient is sampled uniformly in[-max_zernike_amplitude/2, max_zernike_amplitude/2]. Can be overridden perstep()call.zernike_perturbation_amplitude (float) – default amplitude of the per-channel chromatic perturbations, defaults to
0. Can be overridden perstep()call.pupil_size (tuple[int]) – pixel size used to synthesize the super-resolved pupil. The higher the more precise, defaults to
(256, 256). If a singleintis given, a square pupil is considered.apodize (bool) – whether to apodize the PSF to reduce ringing artifacts, defaults to
False.random_rotate (bool) – whether to randomly rotate the PSF, defaults to
False.index_convention (str) – the convention for the Zernike polynomials indexing. Can be either
'noll'(default) or'ansi'.device (str, torch.device) – device where the tensors are allocated and processed, defaults to
'cpu'.dtype (torch.dtype) – data type of the tensors, defaults to
torch.float32.rng (torch.Generator) – pseudo random number generator for reproducibility. Defaults to
None.
- Examples:
>>> from deepinv.physics.generator import DiffractionBlurGenerator >>> generator = DiffractionBlurGenerator((5, 5)) >>> print("\n".join(generator.zernike_polynomials)) Zernike(n = 2, m = 0) -- Defocus Zernike(n = 2, m = -2) -- Oblique Astigmatism Zernike(n = 2, m = 2) -- Vertical Astigmatism Zernike(n = 3, m = -1) -- Vertical Coma Zernike(n = 3, m = 1) -- Horizontal Coma Zernike(n = 3, m = -3) -- Vertical Trefoil Zernike(n = 3, m = 3) -- Oblique Trefoil Zernike(n = 4, m = 0) -- Primary Spherical >>> blur = generator.step() # dict_keys(['filter', 'coeff', 'pupil']) >>> print(blur['filter'].shape) torch.Size([1, 1, 5, 5])
>>> generator = DiffractionBlurGenerator((5, 5), fc=(0.18, 0.20, 0.22)) >>> blur = generator.step(batch_size=2) >>> print(blur['filter'].shape) torch.Size([2, 3, 5, 5]) >>> print(blur['coeff'].shape) # (B, C, K): wavelength-rescaled base + chromatic perturbations torch.Size([2, 3, 8])
- References:
- generate_coeff(batch_size, fc=None, max_zernike_amplitude=None, zernike_perturbation_amplitude=None, n_zernike=None)[source]#
Generate random Zernike coefficients, scaled by cutoff frequency per channel.
- Parameters:
batch_size (int) – number of independent aberration realizations.
fc (torch.Tensor) – already-formatted
(B, C)tensor from class method_format_fc(). IfNone,self.fcis used withbatch_size, producing a(batch_size, K)output (backward-compatible behaviour).max_zernike_amplitude (float) – amplitude of the base coefficients. Defaults to
self.max_zernike_amplitude.zernike_perturbation_amplitude (float) – amplitude of per-channel perturbations. Defaults to
self.zernike_perturbation_amplitude.n_zernike (int) – number of Zernike coefficients to generate. Defaults to
self.n_zernike. Set tolen(used_zernike_index)when called fromstep()with aused_zernike_indexargument.
- Returns:
(batch_size, K)ifC == 1, else(B, C, K).- Return type:
- step(batch_size=1, coeff=None, angle=None, max_zernike_amplitude=None, zernike_perturbation_amplitude=None, seed=None, fc=None, used_zernike_index=None, **kwargs)[source]#
Generate a batch of PSFs with a batch of Zernike coefficients.
The shape of the output PSF is determined by
fcas follows:None:(batch_size, 1, *self.psf_size)or(batch_size, len(self.fc), *self.psf_size).float/ scalar:(batch_size, 1, *self.psf_size).(C,)1D tensor/sequence:(batch_size, C, *self.psf_size).(B, C)2D tensor:(B, C, *self.psf_size).
- Parameters:
batch_size (int) – number of PSFs to generate. Ignored when
fcis a 2D tensor withB > 1(batch size is then read fromfc). Defaults to1.coeff (torch.Tensor) –
Zernike coefficients. Accepted shapes:
None(default): sampled viagenerate_coeff().(B, n_zernike_used): base coefficients per batch element, shared across channels. No rescaling applied. No chromatic perturbation is added.(B, C, n_zernike_used): fully specified per channel. No rescaling applied.
angle (torch.Tensor) –
(batch_size,)angles in degrees for PSF rotation.max_zernike_amplitude (float) – overrides
self.max_zernike_amplitudefor this call only. Only used whencoeffisNone.zernike_perturbation_amplitude (float) – overrides
self.zernike_perturbation_amplitudefor this call only.seed (int) – the seed for the random number generator.
fc (float, tuple[float, ...], list[float], torch.Tensor) – overrides
self.fcfor this call only (does not mutateself.fc). Defaults toNone, in which caseself.fcis used. See class docstring for accepted shapes and their effect on the output PSF shape.used_zernike_index –
subset of Zernike indices to activate for this call. Must be a sub-sequence of
self.zernike_index(same format: ints or(n, m)tuples).None(default) uses the full setself.zernike_index. Useful for varying the active polynomial set without re-instantiating the generator:gen = DiffractionBlurGenerator((31, 31), zernike_index=range(3, 37)) p1 = gen.step(used_zernike_index=range(3, 16)) p2 = gen.step(used_zernike_index=range(3, 28))
- Returns:
dictionary with keys
filter: tensor of size(B, C, H, W)whereBandCare determined byfcas described above,coeff: the Zernike coefficients actually used, shape(B, n_zernike_used)or(B, C, n_zernike_used)wheren_zernike_used = len(used_zernike_index)if specified, elseself.n_zernike,pupil: the pupil function,angle: the random rotation angle in degrees ifrandom_rotateisTrue,fc: tensor of shape(Bf, Cf)with the cutoff frequencies actually used.
- Return type: