def test_zscaler_1(): """ Test ZScaler for floating point round off issues. """ zs = mPSFUtils.ZScaler(0.6, 0.2) assert (zs.getMaxZ() == 7) diff = 1.0e-24 zs = mPSFUtils.ZScaler(0.6 + diff, 0.2 - diff) assert (zs.getMaxZ() == 7) zs = mPSFUtils.ZScaler(0.6 - diff, 0.2 + diff) assert (zs.getMaxZ() == 7)
def measurePSF(movie_name, zfile_name, movie_h5_name, psf_name, want2d=False, aoi_size=12, pixel_size=0.1, z_range=0.75, z_step=0.05): """ movie_name - The name of the movie file. zfile_name - The name of the text file containing z offset data. If this does not exist then the localizations z value will be used. movie_h5_name - The name of the HDF5 file containing the localization information. psf_name - The name of the file to save the measured PSF in. want2d - Measure a 2D PSF. aoi_size - The final AOI size will 2x this number (in pixels). pixel_size - The pixel size in microns. z_range - The z range of the PSF (in microns). The actual z range is 2x z_range (i.e. from -z_range to z_range). z_step - The z granularity of the PSF (in microns). """ # Create z scaling object. z_sclr = measurePSFUtils.ZScaler(z_range, z_step) # Load dax file, z offset file and molecule list file. dax_data = datareader.inferReader(movie_name) z_off = None if os.path.exists(zfile_name): data = numpy.loadtxt(zfile_name, ndmin=2) valid = data[:, 0] z_off = data[:, 1] if want2d: print("Measuring 2D PSF") else: print("Measuring 3D PSF") # Go through the frames identifying good peaks and adding them # to the average psf. # max_z = z_sclr.getMaxZ() average_psf = numpy.zeros((max_z, 2 * aoi_size, 2 * aoi_size)) peaks_used = 0 totals = numpy.zeros(max_z, dtype=numpy.int) with saH5Py.SAH5Py(movie_h5_name) as h5: [dax_x, dax_y, dax_l] = dax_data.filmSize() for curf, locs in h5.localizationsIterator(): # Select localizations in current frame & not near the edges. mask = (locs['x'] > aoi_size) & (locs['x'] < (dax_x - aoi_size - 1)) & ( locs['y'] > aoi_size) & (locs['y'] < (dax_y - aoi_size - 1)) xr = locs['y'][mask] + 1 yr = locs['x'][mask] + 1 # Use the z offset file if it was specified, otherwise use localization z positions. if z_off is None: if (curf == 0): print("Using fit z locations.") zr = locs['z'][mask] else: if (curf == 0): print("Using z offset file.") if (abs(valid[curf]) < 1.0e-6): continue zr = numpy.ones(xr.size) * z_off[curf] ht = locs['height'][mask] # Remove localizations that are too close to each other. mask = iaUtilsC.removeNeighborsMask(xr, yr, 2.0 * aoi_size) print(curf, "peaks in", xr.size, ", peaks out", numpy.count_nonzero(mask)) xr = xr[mask] yr = yr[mask] zr = zr[mask] ht = ht[mask] # Use remaining localizations to calculate spline. image = dax_data.loadAFrame(curf).astype(numpy.float64) for i in range(xr.size): xf = xr[i] yf = yr[i] zf = zr[i] if want2d: zi = 0 else: zi = z_sclr.convert(zf) # Check that the z value is in range if z_sclr.inRange(zi): # Extract PSF. psf = measurePSFUtils.extractAOI(image, aoi_size, xf, yf) # Add to average psf accumulator average_psf[zi, :, :] += psf totals[zi] += 1 # Check that we got at least one valid measurement. # assert (numpy.max(totals) > 0) # Set the PSF to have zero average on the X/Y boundaries. # for i in range(max_z): edge = numpy.concatenate((average_psf[i, 0, :], average_psf[i, -1, :], average_psf[i, :, 0], average_psf[i, :, -1])) average_psf[i, :, :] -= numpy.mean(edge) # Normalize the PSF. # if want2d: max_z = 1 # Note: I think it makes sense to normalize to a sum of 1.0 here as the user may # be using the images of single localizations as the inputs. Unlike beads # we can't assume that they are all the same brightness so normalizing by # the number of events would make even less sense. # for i in range(max_z): print("z plane {0:0d} has {1:0d} samples".format(i, totals[i])) if (totals[i] > 0.0): average_psf[i, :, :] = average_psf[i, :, :] / numpy.sum( numpy.abs(average_psf[i, :, :])) # Normalize to unity maximum height. if (numpy.max(average_psf) > 0.0): average_psf = average_psf / numpy.max(average_psf) else: print("Warning! Measured PSF maxima is zero or negative!") # Save PSF (in image form). if True: with tifffile.TiffWriter("psf.tif") as tf: for i in range(max_z): tf.save(average_psf[i, :, :].astype(numpy.float32)) # Save PSF. # # At least for now the PSFs use nanometers, not microns. # z_range = z_range * 1.0e+3 z_step = z_step * 1.0e+3 if want2d: psf_dict = { "psf": average_psf[0, :, :], "pixel_size": pixel_size, "type": "2D", "version": 2.0 } else: cur_z = -z_range z_vals = [] for i in range(max_z): z_vals.append(cur_z) cur_z += z_step psf_dict = { "psf": average_psf, "pixel_size": pixel_size, "type": "3D", "version": 2.0, "zmin": -z_range, "zmax": z_range, "zvals": z_vals } with open(psf_name, 'wb') as fp: pickle.dump(psf_dict, fp)
def measurePSF(zstack_name, zfile_name, psf_name, pixel_size=0.1, refine=False, z_range=0.75, z_step=0.050, normalize=False): """ zstack_name - The name of the file containing the 2x up-sampled z-stacks. zfile_name - The text file containing the z offsets (in microns) for each frame. psf_name - The name of the file to save the measured PSF in (as a pickled Python dictionary). pixel_size - The pixel size in microns. refine - Align the measured PSF for each bead to the average PSF. z_range - The range the PSF should cover in microns. z_step - The z step size of the PSF. normalize - If true, normalize the PSF to unit height. """ # Create z scaling object. z_sclr = measurePSFUtils.ZScaler(z_range, z_step) max_z = z_sclr.getMaxZ() # Load z-stacks. zstacks = numpy.load(zstack_name) x_size = zstacks[0].shape[0] y_size = zstacks[0].shape[1] # Load z-offsets. z_offset_data = numpy.loadtxt(zfile_name, ndmin=2) is_valid = z_offset_data[:, 0] z_offsets = z_offset_data[:, 1] # Check if the z-stack has fewer frames than the offset file. n_frames = z_offsets.size if (n_frames > zstacks[0].shape[2]): print("Warning! z stack has", n_frames - zstacks[0].shape[2], "fewer frames than the z offset file.") n_frames = zstacks[0].shape[2] # Average stacks in z. psfs = [] for i in range(len(zstacks)): psf = numpy.zeros((max_z, x_size, y_size)) totals = numpy.zeros(max_z, dtype=numpy.int) for j in range(n_frames): # 0.0 = invalid, 1.0 = valid. if (is_valid[j] > 1.0e-3): zi = z_sclr.convert(z_offsets[j]) if z_sclr.inRange(zi): psf[zi, :, :] += zstacks[i][:, :, j] totals[zi] += 1 # Check that we got at least one valid measurement. Totals # is expected to be the same for each PSF. if (i == 0): assert (numpy.max(totals) > 0) # Normalize each PSF z plane by the total counts in the plane. for j in range(max_z): if (i == 0): print("z plane {0:0d} has {1:0d} samples".format(j, totals[j])) if (totals[j] > 0): psf[j, :, :] = psf[j, :, :] / float(totals[j]) # Append to list of PSFs. psfs.append(psf) # Align the PSFs to each other. This should hopefully correct for # any small errors in the input locations, and also for fields of # view that are not completely flat. # if refine: print("Refining PSF alignment.") [average_psf, i_score] = measurePSFUtils.alignPSFs(psfs) else: average_psf = measurePSFUtils.averagePSF(psfs) # Normalize to unity height, if requested. if normalize and (numpy.amax(average_psf) > 0.0): print("Normalizing PSF.") average_psf = average_psf / numpy.amax(average_psf) if not (numpy.amax(average_psf) > 0.0): print("Warning! Measured PSF maxima is zero or negative!") # Save PSF. # # At least for now the PSFs use nanometers, not microns. # z_range = z_range * 1.0e+3 z_step = z_step * 1.0e+3 cur_z = -z_range z_vals = [] for i in range(max_z): z_vals.append(cur_z) cur_z += z_step psf_dict = { "maximum": numpy.amax(average_psf), "psf": average_psf, "pixel_size": pixel_size, "type": "3D", "version": 2.0, "zmin": -z_range, "zmax": z_range, "zvals": z_vals } with open(psf_name, 'wb') as fp: pickle.dump(psf_dict, fp) # Save (normalized) z_stack as tif for inspection purposes. if (numpy.amax(average_psf) > 0.0): average_psf = average_psf / numpy.amax(average_psf) average_psf = average_psf.astype(numpy.float32) with tifffile.TiffWriter(os.path.splitext(psf_name)[0] + ".tif") as tf: for i in range(max_z): tf.save(average_psf[i, :, :])