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)
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()
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()
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
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))
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)
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
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)
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()
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()
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
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()
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)