Beispiel #1
0
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,:,:])
Beispiel #4
0
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'))