Convolvables

Prysm features a rich implemention of Linear Shift Invariant (LSI) system theory. Under this mathematical ideal, the transfer function is the product of the Fourier transform of a cascade of components, and the spatial distribution of intensity is the convolution of a cascade of components. These features are usually used to blur objects or images with Point Spread Functions (PSFs), or model the transfer function of an opto-electronic system. Within prysm there is a class Convolvable which objects and PSFs inherit from. You should rarely need to use the base class, except when subclassing it with your own models or objects or loading a source image.

The built-in convolvable objects are Slits, Pinholes, Tilted Squares, and Siemens Stars. There are also two components, PixelAperture and OLPF, used for system modeling.

[1]:
from prysm import Convolvable, Slit, Pinhole, TiltedSquare, SiemensStar, PixelAperture, OLPF

%matplotlib inline
[2]:
s = Slit(width=1, orientation='crossed')  # diameter, um
p = Pinhole(width=1)
t = TiltedSquare(angle=8, background='white', sample_spacing=0.05, samples=256)  # degrees
star = SiemensStar(spokes=32, sinusoidal=False, background='white', sample_spacing=0.05, samples=256)
pa = PixelAperture(width_x=5)  # diameter, um
ol = OLPF(width_x=5*0.66)

Objects that take a background parameter will be black-on-white for background=white, or white-on-black for background=black. Two objects are convolved via the conv method, which returns self on a new Convolvable instance and is chainable,

[3]:
# monstrosity = s.conv(p).conv(t).conv(star).conv(pa).conv(ol)

Some models require sample spacing and samples parameters while others do not. This is because prysm has many methods of executing an FFT-based Fourier domain convolution under the hood. If an object has a known analytical Fourier transform, the class has an analytic_ft method which has abscissa units of reciprocal microns. If the analytic FT is present, it is used in lieu of numerical data. Models that have analytical Fourier transforms also accept sample_spacing and samples parameters, which are used to define a grid in the spatial domain. If two objects with analytical Fourier transforms are convolved, the output grid will have the finer sample spacing of the two inputs, and the larger span or window width of the two inputs.

The Convolvable constructor takes only four parameters,

[4]:
import numpy as np
x = y = np.linspace(-20,20,256)
z = np.random.uniform(size=256**2).reshape(256,256)
c = Convolvable(data=z, x=x, y=y, has_analytic_ft=False)

has_analytic_ft has a default value of False.

Minimal labor is required to subclass Convolvable. For example, the Pinhole implemention is simply:

[5]:
# need from prysm import mathops as m if you want to actually use this

class Pinhole(Convolvable):
    def __init__(self, width, sample_spacing=0.025, samples=0):
        self.width = width

        # produce coordinate arrays
        if samples > 0:
            ext = samples / 2 * sample_spacing
            x, y = m.linspace(-ext, ext, samples), m.linspace(-ext, ext, samples)
            xv, yv = m.meshgrid(x, y)
            w = width / 2
            # paint a circle on a black background
            arr = m.zeros((samples, samples))
            arr[m.sqrt(xv**2 + yv**2) < w] = 1
        else:
            arr, x, y = None, m.zeros(2), m.zeros(2)

        super().__init__(data=arr, x=x, y=y, has_analytic_ft=True)

    def analytic_ft(self, x, y):
        xq, yq = m.meshgrid(x, y)
        # factor of pi corrects for jinc being modulo pi
        # factor of 2 converts radius to diameter
        rho = m.sqrt(xq**2 + yq**2) * self.width * 2 * m.pi
        return m.jinc(rho).astype(config.precision)

which is less than 20 lines long.

Convolvable objects have a few convenience properties and methods. slice_x and its y variant exist and behave the same as slices on subclasses of OpticalPhase such as Pupil and Interferogram. shape is a convenience wrapper for .data.shape, and support_x, support_y, and support mimic the equivalent diameter properties on OpticalPhase.

Finally, Convolvable objects may be initialized from images,

[6]:
from prysm import sample_files
c = Convolvable.from_file(sample_files('boat.png'), scale=5)  # plate scale in um -- 5microns/pixel
c.data /= 255  # when initializing from files, normalization is left to the user
c.plot2d()
[6]:
(<Figure size 432x288 with 2 Axes>,
 <AxesSubplot:xlabel='Image Plane X [$\\mathrm{\\mu m}$]', ylabel='Image Plane Y [$\\mathrm{\\mu m}$]'>)
../_images/user_guide_Convolvables_10_1.png

and written out as 8 or 16-bit images,

[7]:
p = 'foo.png'  # or jpg, any format imageio can handle
c.save(p, nbits=16)

In practical use, one will generally only use the conv, from_file, and save methods with any degree of regularity. The complete API documentation is below. Attention should be paid to the docstring of conv, as it describes some of the details associated with convolutions in prysm, their accuracy, and when they are used.

[8]:
help(c.conv)
Help on method conv in module prysm.convolution:

conv(other) method of prysm.convolution.Convolvable instance
    Convolves this convolvable with another.

    Parameters
    ----------
    other : `Convolvable`
        A convolvable object

    Returns
    -------
    `Convolvable`
        a convolvable object

    Notes
    -----
    If self and other both have analytic Fourier transforms, no math will be done and the aFTs
    are merged directly.

    If only one of self or other has an analytic Fourier transform, the output grid will be
    defined by the object which does not have an analytic Fourier transform.

    If neither has an analytic transform, the output grid will:
    - span max(self.support, other.support)
    - have sample spacing min(self.sample_spacing, other.sample_spacing)

    This ensures the signal remains Nyquist sampled and (probably) doesn't expand beyond
    the extent of the output window.  The latter condition will be violated when two large
    convolvables are convolved.