def test_align_psfs_1(): """ Test alignment of multiple PSFs to each other. """ pos = [5.0, 6.0, 7.0] maxd = 3.0 psfs = [] for i in range(7): psfs.append( dg.drawGaussiansXYZ((12, 13, 14), numpy.array([pos[0] + int(i / maxd)]), numpy.array([pos[1] + int(i / maxd)]), numpy.array([pos[2] + int(i / maxd)]))) [average_psf, i_score] = mPSFUtils.alignPSFs(psfs) assert (i_score > 2.1) if False: with tifffile.TiffWriter("psf.tif") as tf: tf.save(mPSFUtils.averagePSF(psfs)[6, :, :].astype(numpy.float32)) tf.save(average_psf[6, :, :].astype(numpy.float32))
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, :, :])
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 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,:,:])
def measurePSFBeads(movie_name, zfile_name, beads_file, psf_name, aoi_size=12, pixel_size=0.1, refine=False, z_range=0.6, z_step=0.05): """ movie_name - The name of the movie, presumably a z stack for PSF measurement. zfile_name - The text file containing the z offsets (in microns) for each frame. beads_file - The text file containing the locations of the beads. psf_name - The name of the file to save the measured PSF in (as a pickled Python dictionary). aoi_size - The AOI of interest in pixels. The final AOI is 2x this number. 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. """ # Load the z-offset information for the dax file. # # This is a text file with one line per frame that contains the # z-offset (in microns) for that frame. Each line is a space separated # valid, z_pos pair. If valid if 0 the frame will be ignored, # otherwise it will be used. # z_offsets = numpy.loadtxt(zfile_name) # Create array specifying what frame corresponds to what # Z slice in the PSF. # z_index = measurePSFUtils.makeZIndexArray(z_offsets, z_range, z_step) # Load the locations of the beads. # # This is a text file the contains the locations of the beads that # will be used to construct the PSF. Each line is a space separated # x, y pair of bead locations (in pixels). # # One way to create this file is to look at the bead movie with # visualizer.py and record the center positions of several beads. # data = numpy.loadtxt(beads_file, ndmin=2) bead_x = data[:, 1] + 1 bead_y = data[:, 0] + 1 # Create a reader of the movie. # # We assume that the bead stack was measured with a camera # that does not have a large pixel to pixel variation in # gain and offset. The offset and magnitude are not that # important at we will estimate and subtract the offset # and normalize 1. # # Movie (frame) reader. frame_reader = analysisIO.FrameReaderStd(movie_file=movie_name, camera_gain=1.0, camera_offset=0.0) # Measure PSFs for each bead. # total_samples = None psfs = [] for i in range(bead_x.size): [psf, samples] = measurePSFUtils.measureSinglePSFBeads(frame_reader, z_index, aoi_size, bead_x[i], bead_y[i], zoom=2) # Verify that we have at least one sample per section, because if # we don't this almost surely means something is wrong. if (i == 0): for j in range(samples.size): assert (samples[i] > 0), "No data for PSF z section " + str(i) # Normalize by the number of sample per z section. #for j in range(samples.size): # psf[j,:,:] = psf[j,:,:]/samples[j] # Keep track of total number of samples. if total_samples is None: total_samples = samples else: total_samples += samples psfs.append(psf) # Set the PSF to have zero average on the X/Y boundaries. We are # matching the behavior of spliner.measure_psf here. # sum_psf = measurePSFUtils.sumPSF(psfs) for i in range(sum_psf.shape[0]): mean_edge = measurePSFUtils.meanEdge(sum_psf[i, :, :]) for j in range(len(psfs)): psfs[j][i, :, :] -= mean_edge / float(len(psfs)) # 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.") # Normalize each PSF by the number of z sections. for psf in psfs: for i in range(samples.size): psf[i, :, :] = psf[i, :, :] / samples[i] [average_psf, i_score] = measurePSFUtils.alignPSFs(psfs) else: average_psf = measurePSFUtils.averagePSF(psfs) # Normalize PSF. # # This normalizes the PSF so that sum of the absolute values # of each section is 1.0. This only makes sense if the AOI is # large enough to capture all the photons, which might not be # true. Not clear how important this is as Spliner will fit # for the height anyway. # for i in range(average_psf.shape[0]): print("z plane {0:0d} has {1:0d} samples".format(i, total_samples[i])) section_sum = numpy.sum(numpy.abs(average_psf[i, :, :])) # Do we need this test? We already check that we have at # least one sample per section. if (section_sum > 0.0): average_psf[i, :, :] = average_psf[i, :, :] / section_sum # 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: tif_name = os.path.splitext(psf_name)[0] with tifffile.TiffWriter(tif_name + "_beads.tif") as tf: for i in range(average_psf.shape[0]): tf.save(average_psf[i, :, :].astype(numpy.float32)) # Save PSF. # # For historical reasons all the PSF z values are in nanometers. # At some point this should be fixed. # z_range = 1.0e+3 * z_range z_step = 1.0e+3 * z_step cur_z = -z_range z_vals = [] for i in range(average_psf.shape[0]): z_vals.append(cur_z) cur_z += z_step psf_dict = { "psf": average_psf, "pixel_size": 0.5 * pixel_size, "type": "3D", "version": 1.0, "zmin": -z_range, "zmax": z_range, "zvals": z_vals } pickle.dump(psf_dict, open(psf_name, 'wb'))
def measurePSFBeads(movie_name, zfile_name, beads_file, psf_name, aoi_size = 12, pixel_size = 0.1, refine = False, z_range = 0.6, z_step = 0.05): """ movie_name - The name of the movie, presumably a z stack for PSF measurement. zfile_name - The text file containing the z offsets (in microns) for each frame. beads_file - The text file containing the locations of the beads. psf_name - The name of the file to save the measured PSF in (as a pickled Python dictionary). aoi_size - The AOI of interest in pixels. The final AOI is 2x this number. 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. """ # Load the z-offset information for the dax file. # # This is a text file with one line per frame that contains the # z-offset (in microns) for that frame. Each line is a space separated # valid, z_pos pair. If valid if 0 the frame will be ignored, # otherwise it will be used. # z_offsets = numpy.loadtxt(zfile_name) # Create array specifying what frame corresponds to what # Z slice in the PSF. # z_index = measurePSFUtils.makeZIndexArray(z_offsets, z_range, z_step) # Load the locations of the beads. # # This is a text file the contains the locations of the beads that # will be used to construct the PSF. Each line is a space separated # x, y pair of bead locations (in pixels). # # One way to create this file is to look at the bead movie with # visualizer.py and record the center positions of several beads. # data = numpy.loadtxt(beads_file, ndmin = 2) bead_x = data[:,1] + 1 bead_y = data[:,0] + 1 # Create a reader of the movie. # # We assume that the bead stack was measured with a camera # that does not have a large pixel to pixel variation in # gain and offset. The offset and magnitude are not that # important at we will estimate and subtract the offset # and normalize 1. # # Movie (frame) reader. frame_reader = analysisIO.FrameReaderStd(movie_file = movie_name, camera_gain = 1.0, camera_offset = 0.0) # Measure PSFs for each bead. # total_samples = None psfs = [] for i in range(bead_x.size): [psf, samples] = measurePSFUtils.measureSinglePSFBeads(frame_reader, z_index, aoi_size, bead_x[i], bead_y[i], zoom = 1) # Verify that we have at least one sample per section, because if # we don't this almost surely means something is wrong. if (i == 0): for j in range(samples.size): assert(samples[j] > 0), "No data for PSF z section " + str(j) # Normalize by the number of sample per z section. #for j in range(samples.size): # psf[j,:,:] = psf[j,:,:]/samples[j] # Keep track of total number of samples. if total_samples is None: total_samples = samples else: total_samples += samples psfs.append(psf) # Set the PSF to have zero average on the X/Y boundaries. We are # matching the behavior of spliner.measure_psf here. # sum_psf = measurePSFUtils.sumPSF(psfs) for i in range(sum_psf.shape[0]): mean_edge = measurePSFUtils.meanEdge(sum_psf[i,:,:]) for j in range(len(psfs)): psfs[j][i,:,:] -= mean_edge/float(len(psfs)) # 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.") # Normalize each PSF by the number of z sections. for psf in psfs: for i in range(samples.size): psf[i,:,:] = psf[i,:,:]/samples[i] [average_psf, i_score] = measurePSFUtils.alignPSFs(psfs) else: average_psf = measurePSFUtils.averagePSF(psfs) # Normalize PSF. # # This normalizes the PSF so that sum of the absolute values # of each section is 1.0. This only makes sense if the AOI is # large enough to capture all the photons, which might not be # true. Not clear how important this is as Spliner will fit # for the height anyway. # for i in range(average_psf.shape[0]): print("z plane {0:0d} has {1:0d} samples".format(i, total_samples[i])) section_sum = numpy.sum(numpy.abs(average_psf[i,:,:])) # Do we need this test? We already check that we have at # least one sample per section. if (section_sum > 0.0): average_psf[i,:,:] = average_psf[i,:,:]/section_sum # 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: tif_name = os.path.splitext(psf_name)[0] with tifffile.TiffWriter(tif_name + "_beads.tif") as tf: for i in range(average_psf.shape[0]): tf.save(average_psf[i,:,:].astype(numpy.float32)) # Save PSF. # # For historical reasons all the PSF z values are in nanometers. # At some point this should be fixed. # z_range = 1.0e+3 * z_range z_step = 1.0e+3 * z_step cur_z = -z_range z_vals = [] for i in range(average_psf.shape[0]): 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} pickle.dump(psf_dict, open(psf_name, 'wb'))