Beispiel #1
0
    def test_otf2psf(self):
        """
        Test otf2psf() by verifying that the ideal circular aperture otf obtained with circ_aperture_otf() produces
        the correct psf, obtained by airy_fn()
        :return:
        """
        na = 1.3
        wavelength = 0.465
        dx = 0.061
        nx = 101
        dy = dx
        ny = nx

        fxs = tools.get_fft_frqs(nx, dx)
        fys = tools.get_fft_frqs(ny, dy)
        dfx = fxs[1] - fxs[0]
        dfy = fys[1] - fys[0]

        otf = fit_psf.circ_aperture_otf(fxs[None, :], fys[:, None], na, wavelength)
        psf, (ys, xs) = fit_psf.otf2psf(otf, (dfy, dfx))
        psf = psf / psf.max()

        psf_true = fit_psf.airy_fn(xs[None, :], ys[:, None], [1, 0, 0, na, 0], wavelength)

        self.assertAlmostEqual(np.max(np.abs(psf - psf_true)), 0, 4)
Beispiel #2
0
na = 1.3
pixel_size = 0.065
emission_wavelengths = [0.519, 0.580]
excitation_wavelengths = [0.465, 0.532]

# ############################################
# load OTF data
# ############################################
otf_data_path = "data/2020_05_19_otf_fit_blue.pkl"

with open(otf_data_path, 'rb') as f:
    otf_data = pickle.load(f)
otf_p = otf_data['fit_params']

otf_fn = lambda f, fmax: 1 / (1 + (f / fmax * otf_p[
    0])**2) * psf.circ_aperture_otf(f, 0, na, 2 * na / fmax)

# ############################################
# load affine transformations from DMD to camera
# ############################################
affine_fnames = [
    "data/2021-02-03_09;43;06_affine_xform_blue_z=0.pkl",
    "data/2021-02-03_09;43;06_affine_xform_green_z=0.pkl"
]

affine_xforms = []
for p in affine_fnames:
    with open(p, 'rb') as f:
        affine_xforms.append(pickle.load(f)['affine_xform'])

# ############################################
Beispiel #3
0
def simulated_img(ground_truth,
                  max_photons,
                  cam_gains,
                  cam_offsets,
                  cam_readout_noise_sds,
                  pix_size=None,
                  otf=None,
                  na=1.3,
                  wavelength=0.5,
                  photon_shot_noise=True,
                  bin_size=1,
                  use_otf=False):
    """
    Convert ground truth image (with values between 0-1) to simulated camera image, including the effects of
    photon shot noise and camera readout noise.

    :param use_otf:
    :param ground_truth: Relative intensity values of image
    :param max_photons: Mean photons emitted by ber of photons will be different than expected. Furthermore, due to
    the "blurring" of the point spread function and possible binning of the image, no point in the image
     may realize "max_photons"
    :param cam_gains: gains at each camera pixel
    :param cam_offsets: offsets of each camera pixel
    :param cam_readout_noise_sds: standard deviation characterizing readout noise at each camera pixel
    :param pix_size: pixel size of ground truth image in ums. Note that the pixel size of the output image will be
    pix_size * bin_size
    :param otf: optical transfer function. If None, use na and wavelength to set values
    :param na: numerical aperture. Only used if otf=None
    :param wavelength: wavelength in microns. Only used if otf=None
    :param photon_shot_noise: turn on/off photon shot-noise
    :param bin_size: bin pixels before applying Poisson/camera noise. This is to allow defining a pattern on a
    finer pixel grid.

    :return img:
    :return snr:
    :return max_photons_real:
    """
    if np.any(ground_truth > 1) or np.any(ground_truth < 0):
        warnings.warn(
            'ground_truth image values should be in the range [0, 1] for max_photons to be correct'
        )

    img_size = ground_truth.shape

    if use_otf:
        # get OTF
        if otf is None:
            fx = tools.get_fft_frqs(img_size[1], pix_size)
            fy = tools.get_fft_frqs(img_size[0], pix_size)
            otf = psf.circ_aperture_otf(fx[None, :], fy[:, None], na,
                                        wavelength)

        # blur image with otf/psf
        # todo: maybe should add an "imaging forward model" function to fit_psf.py and call it here.
        gt_ft = fft.fftshift(fft.fft2(fft.ifftshift(ground_truth)))
        img_blurred = max_photons * fft.fftshift(
            fft.ifft2(fft.ifftshift(gt_ft * otf))).real
        img_blurred[img_blurred < 0] = 0
    else:
        img_blurred = max_photons * ground_truth

    # resample image by binning
    img_blurred = tools.bin(img_blurred, (bin_size, bin_size), mode='sum')

    max_photons_real = img_blurred.max()

    # add shot noise
    if photon_shot_noise:
        img_shot_noise = np.random.poisson(img_blurred)
    else:
        img_shot_noise = img_blurred

    # add camera noise and convert from photons to ADU
    readout_noise = np.random.standard_normal(
        img_shot_noise.shape) * cam_readout_noise_sds

    img = cam_gains * img_shot_noise + readout_noise + cam_offsets

    # signal to noise ratio
    sig = cam_gains * img_blurred
    # assuming photon number large enough ~gaussian
    noise = np.sqrt(cam_readout_noise_sds**2 + cam_gains**2 * img_blurred)
    snr = sig / noise

    return img, snr, max_photons_real
Beispiel #4
0
def plot_otf(frq_vects,
             fmax_img,
             otf,
             otf_unc=None,
             to_use=None,
             wf_corrected=None,
             figsize=(20, 10)):
    """
    Plot complete OTF
    :param frq_vects:
    :param fmax_img:
    :param otf:
    :param figsize:
    :return:
    """
    if otf_unc is None:
        otf_unc = np.zeros(otf.shape)

    if to_use is None:
        to_use = np.ones(otf.shape, dtype=np.int)

    nmax1 = int(np.round(0.5 * (otf.shape[1] - 1)))
    nmax2 = int(np.round(0.5 * (otf.shape[2] - 1)))

    fmag = np.linalg.norm(frq_vects, axis=-1)

    fmag_interp = np.linspace(0, fmax_img, 1000)
    # only care about fmax value, so create na/wavelength that give us this
    na = 1
    wavelength = 2 * na / fmax_img
    otf_ideal = fit_psf.circ_aperture_otf(fmag_interp, 0, na, wavelength)

    figh = plt.figure(figsize=figsize)
    grid = plt.GridSpec(2, 6)

    # 1D otf
    ax = plt.subplot(grid[0, :2])
    ylim = [-0.05, 1.2]
    plt.title("otf mag")
    plt.xlabel("Frequency (1/um)")
    plt.ylabel("otf")

    ph_ideal, = plt.plot(fmag_interp, otf_ideal, 'k')
    plt.errorbar(fmag[to_use],
                 np.abs(otf[to_use]),
                 yerr=otf_unc[to_use],
                 color="b",
                 fmt='.')

    colors = ["g", "m", "r", "y", "c"]
    phs = [ph_ideal]
    labels = ["OTF ideal, fmax=%0.2f (1/um)" % fmax_img] + list(range(1, 6))
    # plot main series peaks
    for jj in range(1, 6):
        ph = plt.errorbar(fmag[:, nmax1, nmax2 + jj][to_use[:, nmax1,
                                                            nmax2 + jj]],
                          np.abs(otf[:, nmax1,
                                     nmax2 + jj][to_use[:, nmax1,
                                                        nmax2 + jj]]),
                          yerr=otf_unc[:, nmax1,
                                       nmax2 + jj][to_use[:, nmax1,
                                                          nmax2 + jj]],
                          color=colors[jj - 1],
                          fmt=".")
        phs.append(ph)

        plt.errorbar(fmag[:, nmax1, nmax2 - jj][to_use[:, nmax1, nmax2 - jj]],
                     np.abs(otf[:, nmax1, nmax2 - jj][to_use[:, nmax1,
                                                             nmax2 - jj]]),
                     yerr=otf_unc[:, nmax1, nmax2 - jj][to_use[:, nmax1,
                                                               nmax2 - jj]],
                     color=colors[jj - 1],
                     fmt=".")

    plt.legend(phs, labels)

    xlim = ax.get_xlim()
    plt.plot(xlim, [0, 0], 'k')
    plt.plot([fmax_img, fmax_img], ylim, 'k')
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

    # 1D log scale
    ax = plt.subplot(grid[1, :2])
    plt.title("otf mag (log scale)")
    plt.xlabel("Frequency (1/um)")
    plt.ylabel("otf")

    plt.plot(fmag_interp, otf_ideal, 'k')
    plt.errorbar(fmag[to_use],
                 np.abs(otf[to_use]),
                 yerr=otf_unc[to_use],
                 fmt='.')

    # plot main series peaks
    for jj in range(1, 6):
        plt.errorbar(fmag[:, nmax1, nmax2 + jj][to_use[:, nmax1, nmax2 + jj]],
                     np.abs(otf[:, nmax1, nmax2 + jj][to_use[:, nmax1,
                                                             nmax2 + jj]]),
                     yerr=otf_unc[:, nmax1, nmax2 + jj][to_use[:, nmax1,
                                                               nmax2 + jj]],
                     color=colors[jj - 1],
                     fmt=".")

        plt.errorbar(fmag[:, nmax1, nmax2 - jj][to_use[:, nmax1, nmax2 - jj]],
                     np.abs(otf[:, nmax1, nmax2 - jj][to_use[:, nmax1,
                                                             nmax2 - jj]]),
                     yerr=otf_unc[:, nmax1, nmax2 - jj][to_use[:, nmax1,
                                                               nmax2 - jj]],
                     color=colors[jj - 1],
                     fmt=".")

    xlim = ax.get_xlim()
    ax.plot([fmax_img, fmax_img], ylim, 'k')
    ax.set_xlim(xlim)
    ax.set_ylim([1e-4, 1.2])

    ax.set_yscale('log')

    # show widefield corrected/not peaks
    ax = plt.subplot(grid[1, 4:])
    ylim = [-0.05, 1.2]
    plt.title("otf mag, widefield corrected/not")
    plt.xlabel("Frequency (1/um)")
    plt.ylabel("otf")

    plt.plot(fmag_interp, otf_ideal, 'k')
    phu = plt.errorbar(fmag[to_use],
                       np.abs(otf[to_use]),
                       yerr=otf_unc[to_use],
                       color="b",
                       fmt='.')

    corrected = np.logical_and(wf_corrected, to_use)
    phc = plt.errorbar(fmag[corrected],
                       np.abs(otf[corrected]),
                       yerr=otf_unc[corrected],
                       color="r",
                       fmt=".")

    xlim = ax.get_xlim()
    plt.plot(xlim, [0, 0], 'k')
    plt.plot([fmax_img, fmax_img], ylim, 'k')
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)

    plt.legend([phu, phc], ["uncorrected peaks", "corrected"])

    # 2D otf
    ax = plt.subplot(grid[0, 2:4])
    plt.title("2D otf (log scale)")
    plt.xlabel("fx (1/um)")
    plt.ylabel("fy (1/um)")
    clims = [1e-3, 1]

    frqs_pos = np.array(frq_vects, copy=True)
    y_is_neg = frq_vects[..., 1] < 0
    frqs_pos[y_is_neg] = -frqs_pos[y_is_neg]

    plt.plot([-fmax_img, fmax_img], [0, 0], 'k')
    plt.scatter(frqs_pos[to_use, 0].ravel(),
                frqs_pos[to_use, 1].ravel(),
                c=np.log10(np.abs(otf[to_use]).ravel()),
                norm=matplotlib.colors.Normalize(vmin=np.log10(clims[0]),
                                                 vmax=np.log10(clims[1])))
    cb = plt.colorbar()
    plt.clim(np.log10(clims))

    circ = matplotlib.patches.Circle((0, 0),
                                     radius=fmax_img,
                                     color='k',
                                     fill=0,
                                     ls='-')
    ax.add_artist(circ)
    ax.set_xlim([-fmax_img, fmax_img])
    ax.set_ylim([-0.05 * fmax_img, fmax_img])

    # plot phase
    ax = plt.subplot(grid[1, 2:4])
    plt.title("phase")
    plt.xlabel("Frequency (1/um)")
    plt.ylabel("phase")

    ax.plot(fmag[to_use], np.angle(otf[to_use]).ravel(), '.')

    ylims = [-np.pi - 0.1, np.pi + 0.1]
    ax.set_ylim(ylims)

    # plot 2D phase
    ax = plt.subplot(grid[0, 4:])
    plt.title("2D otf phase")
    plt.xlabel("fx (1/um)")
    plt.ylabel("fy (1/um)")
    clims_phase = [-np.pi - 0.1, np.pi + 0.1]

    plt.plot([-fmax_img, fmax_img], [0, 0], 'k')
    plt.scatter(frqs_pos[to_use, 0].ravel(),
                frqs_pos[to_use, 1].ravel(),
                c=np.angle(otf[to_use]),
                norm=matplotlib.colors.Normalize(vmin=clims_phase[0],
                                                 vmax=clims_phase[1]))
    cb = plt.colorbar()
    plt.clim(clims_phase)

    circ = matplotlib.patches.Circle((0, 0),
                                     radius=fmax_img,
                                     color='k',
                                     fill=0,
                                     ls='-')
    ax.add_artist(circ)
    ax.set_xlim([-fmax_img, fmax_img])
    ax.set_ylim([-0.05 * fmax_img, fmax_img])

    return figh