def test_pupilfn_7():
    """
    Test that PF translation is correct (i.e. independent of size).
    """
    sizes = [10, 20, 40]
    dx = 1.0

    for size in sizes:
        geo = pupilMath.Geometry(size, 0.1, 0.6, 1.5, 1.4)
        pf = geo.createFromZernike(1.0, [[1.3, 2, 2]])

        pf_c = pfFnC.PupilFunction(geometry=geo)
        pf_c.setPF(pf)

        psf_untranslated = numpy.roll(pupilMath.intensity(pf_c.getPSF()),
                                      1,
                                      axis=0)

        pf_c.translate(dx, 0.0, 0.0)
        psf_translated = pupilMath.intensity(pf_c.getPSF())

        if False:
            with tifffile.TiffWriter(
                    storm_analysis.getPathOutputTest(
                        "test_pupilfn_7.tif")) as tf:
                tf.save(psf_untranslated.astype(numpy.float32))
                tf.save(psf_translated.astype(numpy.float32))

        assert numpy.allclose(psf_untranslated, psf_translated)

        pf_c.cleanup()
def test_pupilfn_2():
    """
    Test PF translation.
    """
    dx = 0.5
    dy = 0.25
    dz = 0.2
    geo = pupilMath.Geometry(20, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, 2, 2]])

    pf_c = pfFnC.PupilFunction(geometry=geo)
    pf_c.setPF(pf)

    pf_c.translate(dx, dy, dz)
    psf_c = pupilMath.intensity(pf_c.getPSF())

    defocused = geo.changeFocus(pf, dz)
    translated = geo.translatePf(defocused, dx, dy)
    psf_py = pupilMath.intensity(pupilMath.toRealSpace(translated))

    if False:
        with tifffile.TiffWriter(
                storm_analysis.getPathOutputTest("test_pupilfn_2.tif")) as tf:
            tf.save(psf_c.astype(numpy.float32))
            tf.save(psf_py.astype(numpy.float32))

    assert numpy.allclose(psf_c, psf_py)

    pf_c.cleanup()
def test_pupilfn_4():
    """
    Test PF X derivative (Python library).
    """
    dx = 1.0e-6
    geo = pupilMath.Geometry(20, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, 2, 2]])

    # Calculate derivative of magnitude as a function of x.
    psf_py = pupilMath.toRealSpace(pf)
    psf_py_dx = pupilMath.toRealSpace(geo.dx(pf))
    mag_dx_calc = 2.0 * (numpy.real(psf_py) * numpy.real(psf_py_dx) +
                         numpy.imag(psf_py) * numpy.imag(psf_py_dx))

    # Estimate derivative using (f(x+dx) - f(x))/dx
    mag = pupilMath.intensity(psf_py)
    translated = geo.translatePf(pf, dx, 0.0)
    mag_dx_est = (pupilMath.intensity(pupilMath.toRealSpace(translated)) -
                  mag) / dx

    if False:
        with tifffile.TiffWriter(
                storm_analysis.getPathOutputTest("test_pupilfn_4.tif")) as tf:
            #tf.save(mag.astype(numpy.float32))
            tf.save(mag_dx_calc.astype(numpy.float32))
            tf.save(mag_dx_est.astype(numpy.float32))
            tf.save(numpy.abs(mag_dx_calc - mag_dx_est).astype(numpy.float32))

    assert numpy.allclose(mag_dx_calc, mag_dx_est, atol=1.0e-6)
Exemple #4
0
def test_otf_scaler_2():
    """
    Test that the C and the Python libraries agree on the calculation
    of an OTF scaled PSF at multiple z values.
    """
    otf_sigma = 1.8

    geo = pupilMath.Geometry(128, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, 2, 2]])

    pf_c = pfFnC.PupilFunction(geometry=geo)
    pf_c.setPF(pf)

    gsf = geo.gaussianScalingFactor(otf_sigma)

    otf_sc = otfSC.OTFScaler(size=geo.size)
    otf_sc.setScale(gsf)

    for dz in [-0.2, 0.1, 0.0, 0.1, 0.2]:
        pf_c.translateZ(dz)
        psf_c = pf_c.getPSFIntensity()
        psf_c = otf_sc.scale(psf_c)

        psf_py = geo.pfToPSF(pf, [dz], scaling_factor=gsf)

        assert numpy.allclose(psf_c, psf_py)

    pf_c.cleanup()
    otf_sc.cleanup()
Exemple #5
0
def test_otf_scaler_1():
    """
    Test that the C and the Python libraries agree on the calculation
    of an OTF scaled PSF.
    """
    otf_sigma = 1.8

    geo = pupilMath.Geometry(128, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, 2, 2]])

    pf_c = pfFnC.PupilFunction(geometry=geo)
    pf_c.setPF(pf)

    otf_sc = otfSC.OTFScaler(size=geo.size)

    gsf = geo.gaussianScalingFactor(otf_sigma)
    psf_py = geo.pfToPSF(pf, [0.0], scaling_factor=gsf)

    psf_c = pf_c.getPSFIntensity()
    otf_sc.setScale(gsf)
    psf_c = otf_sc.scale(psf_c)

    if False:
        with tifffile.TiffWriter(
                storm_analysis.getPathOutputTest(
                    "test_otf_scaler_1.tif")) as tf:
            tf.save(psf_c.astype(numpy.float32))
            tf.save(psf_py.astype(numpy.float32))

    assert numpy.allclose(psf_c, psf_py)

    pf_c.cleanup()
    otf_sc.cleanup()
Exemple #6
0
    def __init__(self, sim_fp, x_size, y_size, i3_data, nm_per_pixel, zmn, wavelength = 600, refractive_index = 1.5, numerical_aperture = 1.4):
        """
        zmn is a list of lists containing the zernike mode terms, e.g.
            [[1.3, 2, 2]] for pure astigmatism.
        wavelength is the mean emission wavelength in nm.
        """
        PSF.__init__(self, sim_fp, x_size, y_size, i3_data, nm_per_pixel)
        self.saveJSON({"psf" : {"class" : "PupilFunction",
                                "nm_per_pixel" : str(nm_per_pixel),
                                "numerical_aperture" : str(numerical_aperture),
                                "refactrive_index" : str(refractive_index),
                                "wavelength" : str(wavelength),
                                "zmn" : str(zmn)}})

        self.geo = pupilMath.Geometry(int(4.0/(nm_per_pixel * 0.001)),
                                      nm_per_pixel * 0.001,
                                      wavelength * 0.001,
                                      refractive_index,
                                      numerical_aperture)
        self.pf = self.geo.createFromZernike(1.0, zmn)
        self.psf_size = self.geo.r.shape[0]

        if ((self.psf_size%2)==0):
            self.margin = int(self.psf_size/2) + 1
        else:
            self.margin = int(self.psf_size/2) + 2

        print("psf size", self.psf_size)

        self.im_size_x = self.x_size + 2 * self.margin
        self.im_size_y = self.y_size + 2 * self.margin
Exemple #7
0
def test_pupilfn_6():
    """
    Test PF Z derivative (C library).
    """
    dz = 1.0e-6
    geo = pupilMath.Geometry(20, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, 2, 2]])

    pf_c = pfFnC.PupilFunction(geometry=geo)
    pf_c.setPF(pf)

    # Calculate derivative of magnitude as a function of z.
    psf_c = pf_c.getPSF()
    psf_c_dz = pf_c.getPSFdz()
    mag_dz_calc = 2.0 * (numpy.real(psf_c) * numpy.real(psf_c_dz) +
                         numpy.imag(psf_c) * numpy.imag(psf_c_dz))

    # Estimate derivative using (f(z+dz) - f(z))/dz
    mag = pupilMath.intensity(psf_c)
    pf_c.translate(0.0, 0.0, dz)
    mag_dz_est = (pupilMath.intensity(pf_c.getPSF()) - mag) / dz

    if False:
        with tifffile.TiffWriter(
                storm_analysis.getPathOutputTest("test_pupilfn_6.tif")) as tf:
            #tf.save(mag.astype(numpy.float32))
            tf.save(mag_dz_calc.astype(numpy.float32))
            tf.save(mag_dz_est.astype(numpy.float32))
            tf.save(numpy.abs(mag_dz_calc - mag_dz_est).astype(numpy.float32))

    assert (numpy.max(numpy.abs(mag_dz_calc - mag_dz_est))) < 1.0e-6

    pf_c.cleanup()
def test_pupilfn_3():
    """
    Test PF X derivative (C library).
    """
    dx = 1.0e-6
    geo = pupilMath.Geometry(20, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, 2, 2]])

    pf_c = pfFnC.PupilFunction(geometry=geo)
    pf_c.setPF(pf)

    # Calculate derivative of magnitude as a function of x.
    psf_c = pf_c.getPSF()
    psf_c_dx = pf_c.getPSFdx()
    mag_dx_calc = 2.0 * (numpy.real(psf_c) * numpy.real(psf_c_dx) +
                         numpy.imag(psf_c) * numpy.imag(psf_c_dx))

    # Estimate derivative using (f(x+dx) - f(x))/dx
    mag = pupilMath.intensity(psf_c)
    pf_c.translate(dx, 0.0, 0.0)
    mag_dx_est = (pupilMath.intensity(pf_c.getPSF()) - mag) / dx

    if False:
        with tifffile.TiffWriter(
                storm_analysis.getPathOutputTest("test_pupilfn_3.tif")) as tf:
            #tf.save(mag.astype(numpy.float32))
            tf.save(mag_dx_calc.astype(numpy.float32))
            tf.save(mag_dx_est.astype(numpy.float32))
            tf.save(numpy.abs(mag_dx_calc - mag_dx_est).astype(numpy.float32))

    assert numpy.allclose(mag_dx_calc, mag_dx_est, atol=1.0e-6)

    pf_c.cleanup()
def makePSF(filename, size, pixel_size, zmn, zrange, zstep):
    """
    pixel_size - pixel size in microns.
    zmn - Zernike coefficients.
    zrange - The final zrange will be +- zrange (microns).
    zstep - The z step size in microns.
    """
    #
    # Physical constants. Note that these match the default values for
    # simulator.psf.PupilFunction().
    #
    wavelength = 0.6  # Fluorescence wavelength in microns.
    imm_index = 1.5  # Immersion media index (oil objective).
    NA = 1.4  # Numerical aperture of the objective.

    # Create geometry object.
    geo = pupilMath.Geometry(size, pixel_size, wavelength, imm_index, NA)

    # Create PF.
    pf = geo.createFromZernike(1.0, zmn)

    # Normalize to have height 1.0 (at z = 0.0).
    psf = pupilMath.intensity(pupilMath.toRealSpace(pf))
    pf = pf * 1.0 / math.sqrt(numpy.max(psf))

    # Verify normalization.
    print("Height:", numpy.max(pupilMath.intensity(pupilMath.toRealSpace(pf))))

    # Create a PSF at each z value.
    z_values = numpy.arange(-zrange, zrange + 0.5 * zstep, zstep)
    #print(z_values)

    if ((z_values.size % 2) == 0):
        print("The number of z slices must be an odd number.")
        assert False, "PSF creation failed."

    psf = numpy.zeros((z_values.size, size, size))
    for i, z in enumerate(z_values):
        defocused = geo.changeFocus(pf, z)
        psf[i, :, :] = pupilMath.intensity(pupilMath.toRealSpace(defocused))

    # Pickle and save.
    psf_dict = {
        "psf": psf,
        "pixel_size": pixel_size,
        "zmax": 1000.0 * zrange,
        "zmin": -1000.0 * zrange
    }

    with open(filename, 'wb') as fp:
        pickle.dump(psf_dict, fp)

    # Also save a .tif version.
    with tifffile.TiffWriter("psf.tif") as tf:
        for i in range(psf.shape[0]):
            tf.save(psf[i, :, :].astype(numpy.float32))
Exemple #10
0
    def __init__(self,
                 sim_fp,
                 x_size,
                 y_size,
                 h5_data,
                 nm_per_pixel,
                 zmn,
                 wavelength=pf_wavelength,
                 refractive_index=pf_refractive_index,
                 numerical_aperture=pf_numerical_aperture,
                 pf_size=pf_size,
                 bead_z_center=None,
                 otf_sigma=None,
                 sample_index=None):
        """
        zmn is a list of lists containing the zernike mode terms, e.g.
            [[1.3, 2, 2]] for pure astigmatism.
        wavelength is the mean emission wavelength in nm.

        bead_z_center is the height of the bead above the coverslip in microns.
        otf_sigma is the sigma to use for Gaussian OTF scaling.
        sample_index is the index of the sample media.

        If you specify sample_index you also need to specify z_center.
        """
        super(PupilFunctionScalarCalibration,
              self).__init__(sim_fp, x_size, y_size, h5_data, nm_per_pixel,
                             pf_size)
        self.saveJSON({
            "psf": {
                "class": "PupilFunctionScalarCalibration",
                "bead_z_center": str(bead_z_center),
                "nm_per_pixel": str(nm_per_pixel),
                "numerical_aperture": str(numerical_aperture),
                "otf_sigma": str(otf_sigma),
                "pf_size": str(pf_size),
                "refactrive_index": str(refractive_index),
                "sample_index": str(sample_index),
                "wavelength": str(wavelength),
                "zmn": str(zmn)
            }
        })

        self.geo = pupilMath.Geometry(pf_size, nm_per_pixel * 0.001,
                                      wavelength * 0.001, refractive_index,
                                      numerical_aperture)

        self.pf = self.geo.createFromZernike(1.0, zmn)

        if otf_sigma is not None:
            self.otf_scaler = self.geo.gaussianScalingFactor(otf_sigma)

        self.ab_fn = None
        if sample_index is not None:
            self.ab_fn = self.geo.aberration(bead_z_center, sample_index)
Exemple #11
0
    def __init__(self,
                 sim_fp,
                 x_size,
                 y_size,
                 h5_data,
                 nm_per_pixel,
                 zmn,
                 wavelength=pf_wavelength,
                 refractive_index=pf_refractive_index,
                 numerical_aperture=pf_numerical_aperture,
                 pf_size=pf_size,
                 geo_sim_pf=True):
        """
        zmn is a list of lists containing the zernike mode terms, e.g.
            [[1.3, 2, 2]] for pure astigmatism.
        wavelength is the mean emission wavelength in nm.
        """
        super(PupilFunction, self).__init__(sim_fp, x_size, y_size, h5_data,
                                            nm_per_pixel)
        self.saveJSON({
            "psf": {
                "class": "PupilFunction",
                "geo_sim_pf": str(geo_sim_pf),
                "nm_per_pixel": str(nm_per_pixel),
                "numerical_aperture": str(numerical_aperture),
                "pf_size": str(pf_size),
                "refactrive_index": str(refractive_index),
                "wavelength": str(wavelength),
                "zmn": str(zmn)
            }
        })

        if geo_sim_pf:
            self.geo = pupilMath.GeometrySim(pf_size, nm_per_pixel * 0.001,
                                             wavelength * 0.001,
                                             refractive_index,
                                             numerical_aperture)
        else:
            self.geo = pupilMath.Geometry(pf_size, nm_per_pixel * 0.001,
                                          wavelength * 0.001, refractive_index,
                                          numerical_aperture)

        self.pf = self.geo.createFromZernike(1.0, zmn)
        self.psf_size = self.geo.r.shape[0]

        if ((self.psf_size % 2) == 0):
            self.margin = int(self.psf_size / 2) + 1
        else:
            self.margin = int(self.psf_size / 2) + 2

        self.im_size_x = self.x_size + 2 * self.margin
        self.im_size_y = self.y_size + 2 * self.margin
Exemple #12
0
def test_pupil_math_1():
    """
    Test GeometryC, intensity, no scaling.
    """
    geo = pupilMath.Geometry(20, 0.1, 0.6, 1.5, 1.4)
    geo_c = pupilMath.GeometryC(20, 0.1, 0.6, 1.5, 1.4)

    pf = geo.createFromZernike(1.0, [[1.3, -1, 3], [1.3, -2, 2]])
    z_vals = numpy.linspace(-1.0, 1.0, 10)

    psf_py = geo.pfToPSF(pf, z_vals)
    psf_c = geo_c.pfToPSF(pf, z_vals)

    assert numpy.allclose(psf_c, psf_py)
Exemple #13
0
def test_pupil_math_3():
    """
    Test GeometryC, intensity, scaling.
    """
    geo = pupilMath.Geometry(20, 0.1, 0.6, 1.5, 1.4)
    geo_c = pupilMath.GeometryC(20, 0.1, 0.6, 1.5, 1.4)

    pf = geo.createFromZernike(1.0, [[1.3, -1, 3], [1.3, -2, 2]])
    z_vals = numpy.linspace(-1.0, 1.0, 10)

    gsf = geo.gaussianScalingFactor(1.8)
    psf_py = geo.pfToPSF(pf, z_vals, scaling_factor=gsf)
    psf_c = geo_c.pfToPSF(pf, z_vals, scaling_factor=gsf)

    assert numpy.allclose(psf_c, psf_py)
def test_pupilfn_10():
    """
    Test C library PSF intensity calculation.
    """
    geo = pupilMath.Geometry(20, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, -1, 3], [1.3, -2, 2]])

    pf_c = pfFnC.PupilFunction(geometry=geo)
    pf_c.setPF(pf)

    psf_c = pf_c.getPSFIntensity()
    psf_py = pupilMath.intensity(pupilMath.toRealSpace(pf))

    assert numpy.allclose(psf_c, psf_py)

    pf_c.cleanup()
Exemple #15
0
def makePSFAndPF(zmin, zmax, zstep):
    """
    Creates the PSF and PF used for testing.
    """
    size = 20
    geo = pupilMath.Geometry(size, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, 2, 2]])

    z_values = numpy.arange(zmin, zmax + 0.5 * zstep, zstep)

    psf = numpy.zeros((z_values.size, size, size))
    for i, z in enumerate(z_values):
        defocused = geo.changeFocus(pf, z)
        psf[i, :, :] = pupilMath.intensity(pupilMath.toRealSpace(defocused))

    return [psf, geo, pf]
def test_pupilfn_11():
    """
    Test C library PF Z translation function.
    """
    geo = pupilMath.Geometry(20, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, -1, 3], [1.3, -2, 2]])

    pf_c = pfFnC.PupilFunction(geometry=geo)
    pf_c.setPF(pf)

    for dz in [-0.2, 0.1, 0.0, 0.1, 0.2]:
        pf_c.translateZ(dz)
        psf_c = pf_c.getPSFIntensity()

        defocused = geo.changeFocus(pf, dz)
        psf_py = pupilMath.intensity(pupilMath.toRealSpace(defocused))

        assert numpy.allclose(psf_c, psf_py)

    pf_c.cleanup()
Exemple #17
0
    def __init__(self, psf_filename=None, zmax=None, zmin=None, **kwds):
        kwds["psf_filename"] = psf_filename
        super(CRPupilFn, self).__init__(**kwds)

        # Load the pupil function data.
        with open(psf_filename, 'rb') as fp:
            pf_data = pickle.load(fp)

        # Get the pupil function and verify that the type is correct.
        pf = pf_data['pf']
        assert (pf.dtype == numpy.complex128)

        # Get the pupil function pixel size.
        self.pupil_size = pf.shape[0]

        # Create geometry object.
        if pf_data.get("geo_sim_pf", False):
            geo = pupilMath.GeometrySim(self.pupil_size, pf_data['pixel_size'],
                                        pf_data['wavelength'],
                                        pf_data['immersion_index'],
                                        pf_data['numerical_aperture'])
        else:
            geo = pupilMath.Geometry(self.pupil_size, pf_data['pixel_size'],
                                     pf_data['wavelength'],
                                     pf_data['immersion_index'],
                                     pf_data['numerical_aperture'])

        # Create C pupil function object.
        self.pupil_fn_c = pupilFnC.PupilFunction(geo)
        self.pupil_fn_c.setPF(pf)

        # Additional initializations.
        self.zmax = zmax * 1.0e+3
        self.zmin = zmin * 1.0e+3

        # CR weights approximately every 25nm.
        self.n_zvals = int(round((self.zmax - self.zmin) / 25.0))

        self.delta_xy = self.pixel_size
        self.delta_z = 1000.0
Exemple #18
0
    def __init__(self, pf_filename = None, zmin = None, zmax = None, **kwds):
        """
        Technically a pupil function would cover any z range, but in fitting
        we are limit it to a finite range. Also, zmin and zmax should be 
        specified in nanometers.
        """
        super(PupilFunction, self).__init__(**kwds)
        self.zmax = zmax
        self.zmin = zmin

        # Load the pupil function data.
        with open(pf_filename, 'rb') as fp:
            pf_data = pickle.load(fp)

        # Get the pupil function and verify that the type is correct.
        pf = pf_data['pf']
        assert (pf.dtype == numpy.complex128)

        # Get the pupil function size and pixel size.
        self.pixel_size = pf_data['pixel_size']
        self.pupil_size = pf.shape[0]

        # Create geometry object.
        if pf_data.get("geo_sim_pf", False):
            geo = pupilMath.GeometrySim(self.pupil_size,
                                        self.pixel_size,
                                        pf_data['wavelength'],
                                        pf_data['immersion_index'],
                                        pf_data['numerical_aperture'])
        else:
            geo = pupilMath.Geometry(self.pupil_size,
                                     self.pixel_size,
                                     pf_data['wavelength'],
                                     pf_data['immersion_index'],
                                     pf_data['numerical_aperture'])

        # Create C pupil function object.
        self.pupil_fn_c = pupilFnC.PupilFunction(geo)
        self.pupil_fn_c.setPF(pf)
def test_pupilfn_1():
    """
    Test that the C and the Python library agree on the calculation
    of the untranslated PSF.
    """
    geo = pupilMath.Geometry(20, 0.1, 0.6, 1.5, 1.4)
    pf = geo.createFromZernike(1.0, [[1.3, 2, 2]])

    pf_c = pfFnC.PupilFunction(geometry=geo)
    pf_c.setPF(pf)

    psf_c = pupilMath.intensity(pf_c.getPSF())
    psf_py = pupilMath.intensity(pupilMath.toRealSpace(pf))

    if False:
        with tifffile.TiffWriter(
                storm_analysis.getPathOutputTest("test_pupilfn_1.tif")) as tf:
            tf.save(psf_c.astype(numpy.float32))
            tf.save(psf_py.astype(numpy.float32))

    assert numpy.allclose(psf_c, psf_py)

    pf_c.cleanup()
Exemple #20
0
def makePupilFunction(filename,
                      size,
                      pixel_size,
                      zmn,
                      z_offset=0.0,
                      geo_sim_pf=True):
    """
    geo_sim_pf - Use the 'simulation' PF with 1/2 the pixel size.
    pixel_size - pixel size in microns.
    zmn - Zernike coefficients.
    z_offset - Amount to change the focus by in microns.
    """
    # This is a requirement of the C library.
    assert ((size % 2) == 0)

    # Physical constants. Note that these match the default values for
    # simulator.psf.PupilFunction().
    #
    wavelength = 1.0e-3 * simPSF.pf_wavelength  # Fluorescence wavelength in microns.
    imm_index = simPSF.pf_refractive_index  # Immersion media index (oil objective).
    NA = simPSF.pf_numerical_aperture  # Numerical aperture of the objective.

    # Create geometry object.
    if geo_sim_pf:
        geo = pupilMath.GeometrySim(size, pixel_size, wavelength, imm_index,
                                    NA)

    else:
        geo = pupilMath.Geometry(size, pixel_size, wavelength, imm_index, NA)

    # Create PF.
    pf = geo.createFromZernike(1.0, zmn)

    # Normalize to have height 1.0.
    psf = pupilMath.intensity(pupilMath.toRealSpace(pf))
    pf = pf * 1.0 / math.sqrt(numpy.max(psf))

    # Verify normalization.
    print("Height:", numpy.max(pupilMath.intensity(pupilMath.toRealSpace(pf))))

    # Heh, if zmn is an empty list the pupil function will be perfectly
    # symmetric at z = 0 and the solver will fail because dz = 0. So we
    # solve this we adding a little noise.
    if (len(zmn) == 0):
        print("Plane wave PF detected! Adding noise to break z = 0 symmetry!")
        n_mag = numpy.real(pf) * 1.0e-3
        pf = pf + n_mag * (numpy.random.uniform(size=pf.shape) - 0.5)

    # Change focus by z_offset.
    #
    # The convention is that if z_offset + localization z is the final
    # z position, so if localization z is = -z_offset then you will get
    # the PSF at z = 0.
    #
    pf = geo.changeFocus(pf, -z_offset)

    # Pickle and save.
    pfn_dict = {
        "pf": pf,
        "pixel_size": pixel_size,
        "geo_sim_pf": geo_sim_pf,
        "wavelength": wavelength,
        "immersion_index": imm_index,
        "numerical_aperture": NA
    }

    with open(filename, 'wb') as fp:
        pickle.dump(pfn_dict, fp)