示例#1
0
def fill(tar, loc, dim, val=0, center=False):
    """Fill target with value in ROI"""
    loc = YX(loc)
    dim = HW(dim)
    tar_dim = HW(tar.shape)
    if center:
        loc -= dim // 2
    tar_roi, _ = clip2d(loc.x, tar_dim.w, dim.w, loc.y, tar_dim.h, dim.h)
    if tar_roi is not None:
        tar[tar_roi] = val
示例#2
0
def set_with_mask_in_place(tar, mask, value, loc=XY(0, 0), center=False):
    loc = YX(loc)
    tar_dim = HW(tar.shape)
    msk_dim = HW(mask.shape)
    if center:
        loc -= msk_dim // 2
    tar_roi, msk_roi = clip2d(loc.x, tar_dim.w, msk_dim.w, loc.y, tar_dim.h, msk_dim.h)
    if tar_roi is not None and msk_roi is not None:
        subset = tar[tar_roi]
        subset[mask[msk_roi]] = value
        tar[tar_roi] = subset
示例#3
0
def _step_5_radiometry(peak_df, field_df, aligned_raw_chcy_ims,
                       sigproc_params):
    """
    Radiometry measures the area under the curve of the PSF
    """

    n_outchannels, n_inchannels, n_cycles, dim = sigproc_params.channels_cycles_dim
    n_peaks = len(peak_df)

    hat_rad = sigproc_params.hat_rad
    brim_rad = sigproc_params.hat_rad + 1
    hat_mask, brim_mask = _hat_masks(hat_rad, brim_rad)

    signal_radmat = np.zeros((n_peaks, n_outchannels, n_cycles))
    noise_radmat = np.zeros((n_peaks, n_outchannels, n_cycles))
    localbg_radmat = np.zeros((n_peaks, n_outchannels, n_cycles))

    trace_dim = HW(brim_mask.shape) + HW(4, 4)
    localbg_mask = np.zeros(trace_dim)
    imops.accum_inplace(localbg_mask,
                        brim_mask,
                        loc=trace_dim / 2,
                        center=True)
    localbg_mask = localbg_mask > 0

    medians_by_chcy = (field_df.set_index(["channel_i",
                                           "cycle_i"]).sort_index().bg_median)

    # This triple-nested loop is a potential point of optimization
    # But it is probably dwarfed by the time it takes to do a center-of-mass
    # calculation for each peak on each channel/cycle.
    # Note that I was previously doing the COM calculation only
    # once for a cycle stack but since I don't do sub-pixel
    # alignment on each cycle I think it will be more accurate
    # to re-do the COM calculation on every cycle.
    for peak_i, peak_row in peak_df.iterrows():
        for outch in range(n_outchannels):
            for cycle in range(n_cycles):
                signal, noise, localbg = _radiometry(
                    aligned_raw_chcy_ims[outch, cycle],
                    XY(peak_row.aln_x, peak_row.aln_y),
                    trace_dim,
                    medians_by_chcy[outch, cycle],
                    localbg_mask,
                )
                signal_radmat[peak_i, outch, cycle] = signal
                noise_radmat[peak_i, outch, cycle] = noise
                localbg_radmat[peak_i, outch, cycle] = localbg

    # Any peak that has a NAN in it anywhere, zero it out
    _remove_nans_from_radiometry(signal_radmat, noise_radmat, localbg_radmat)

    return signal_radmat, noise_radmat, localbg_radmat
示例#4
0
def accum_inplace(tar, src, loc=XY(0, 0), center=False):
    """
    Accumulate the src image into the (tar)get
    at loc with optional source centering
    """
    loc = YX(loc)
    tar_dim = HW(tar.shape)
    src_dim = HW(src.shape)
    if center:
        loc -= src_dim // 2
    tar_roi, src_roi = clip2d(loc.x, tar_dim.w, src_dim.w, loc.y, tar_dim.h, src_dim.h)
    if tar_roi is not None and src_roi is not None:
        tar[tar_roi] += src[src_roi]
示例#5
0
def extract_trace(imstack, loc, dim, center=True):
    """Extract a trace of dim at loc from the stack"""
    imstack = stack(imstack)
    dim = HW(dim)
    loc = YX(loc)
    roi = ROI(loc, dim, center=center)
    return imstack[:, roi[0], roi[1]]
示例#6
0
def peak_find_chcy_ims(chcy_ims, approx_psf, cycle_i, subpixel=True):
    """
    Previous version of this code depended on the channels being
    balanced relative to one another. But this early-stage channel
    balancing turned out to be problematic.

    This new code instead peak finds on each channel independently
    and then reconciles the peak locations by unioning the
    lists of peaks and de-duping them using an approximate measure
    of distance as the de-dupe key.

    If subpixel is not True uses the faster pixel-only accuracy.

    Returns:
        locs: ndarray (n_peaks, 2)  where the second dim is in (y, x) order
    """

    # Use more than one cycle to improve the quality of the sub-pixel estimate
    # But then discard peaks that are off after cycle 1??

    n_channels = chcy_ims.shape[0]
    locs_per_channel = []
    for ch_i in range(n_channels):
        im = chcy_ims[ch_i, cycle_i, :, :]
        try:
            locs = pixel_peak_find_one_im(im, approx_psf)
            if subpixel:
                locs = _pixel_to_subpixel_one_im(im, HW(approx_psf.shape),
                                                 locs.astype(int))
        except Exception:
            # Failure during peak find, no peaks recorded for this frame.
            locs = np.zeros((0, 2))
        locs_per_channel += [locs]

    union_locs = np.vstack(locs_per_channel)

    # UNION and de-dupe where <= 1.0 pixel is considered a duplicate
    dists = cdist(union_locs, union_locs, "euclidean")
    n_locs = union_locs.shape[0]
    if n_locs == 0:
        return np.zeros((0, 2))

    # Set self-distances to large so that they will not be found as "closest"
    dists[np.arange(n_locs), np.arange(n_locs)] = 1e6
    closest_i = np.argmin(dists, axis=1)
    closest_d = dists[np.arange(n_locs), closest_i]

    # Any peaks with another peak within 1 pixel is a candidate for de-dupeing
    dedupe_mask = closest_d <= 1.0

    a_iz = closest_i[dedupe_mask]
    b_iz = np.arange(n_locs)[dedupe_mask]
    c_iz = np.arange(n_locs)[
        ~dedupe_mask]  # Those with no mate (in one channel only)

    # Of those pairs we have to keep one or the other so we take the
    # one with the lower index value.
    keep_iz = np.where(a_iz < b_iz, a_iz, b_iz)
    keep_iz = np.concatenate((keep_iz, c_iz))
    return union_locs[np.unique(keep_iz)]
示例#7
0
    def it_finds_sub_pixel_exactly_under_ideal_conditions():
        """
        Test the helper _sub_pixel_peak_find instead of sub_pixel_peak_find
        because we don't want to have to reconcile the peak ordering
        from the synth with the arbitrary order they are found by the
        peak finder
        """

        with synth.Synth(dim=(512, 512), n_cycles=3) as s:
            true_n_peaks = 100
            peaks = (
                synth.PeaksModelGaussianCircular(n_peaks=true_n_peaks)
                .amps_constant(1000)
                .widths_uniform(1.5)
                .locs_grid()
                .locs_add_random_subpixel()
            )
            s.zero_aln_offsets()
            chcy_ims = s.render_chcy()

        chcy_mean_im = np.mean(chcy_ims, axis=(0, 1))
        locs = peak_find._pixel_to_subpixel_one_im(
            chcy_mean_im, HW(peaks.mea, peaks.mea), peaks.locs.astype(int)
        )
        dists = np.linalg.norm(locs - peaks.locs, axis=1)
        assert np.all(dists < 0.01)
示例#8
0
def composite(ims, offsets, start_accum=None, limit_accum=None):
    """Build up a composite image from the stack with offsets"""

    # FIND the largest offset and add a border around the image of that size
    border_size = np.abs(offsets).max()
    border = HW(border_size, border_size)
    comp_dim = HW(ims[0].shape) + border * 2
    comp = np.zeros(comp_dim)
    comp_count = 0
    for i, (im, offset) in enumerate(zip(ims, offsets)):
        if start_accum is not None and i < start_accum:
            continue
        if limit_accum is not None and comp_count >= limit_accum:
            break
        accum_inplace(comp, src=im, loc=border - YX(offset))
        comp_count += 1
    return comp, border_size
示例#9
0
def edge_fill(tar, loc, dim, val=0, center=False):
    """Fill rect edge target with value in ROI"""
    loc = YX(loc)
    dim = HW(dim)
    tar_dim = HW(tar.shape)
    if center:
        loc -= dim // 2
    tar_roi, _ = clip2d(loc.x, tar_dim.w, dim.w, loc.y, tar_dim.h, dim.h)
    if tar_roi is not None:
        # Bottom
        tar[tar_roi[0].start, tar_roi[1].start : tar_roi[1].stop] = val

        # Top
        tar[tar_roi[0].stop - 1, tar_roi[1].start : tar_roi[1].stop] = val

        # Left
        tar[tar_roi[0].start : tar_roi[0].stop, tar_roi[1].start] = val

        # Right
        tar[tar_roi[0].start : tar_roi[0].stop, tar_roi[1].stop - 1] = val
示例#10
0
def align(im_stack, return_shifted_ims=False, bounds=None):
    """
    Align the image stack (1 pixel accuracy) relative to the first frame in the stack
    Arguments:
        im_stack (3 dimensions)
        return_shifted_ims:
            If True, also return the shifted images truncated to the common
            region of interest
        bounds: If not None limit the search space

    Returns:
        list of YX tuples
        shifted_ims (optional)
    """
    check.array_t(im_stack, ndim=3, dtype=np.float64)
    n_cycles, mea_h, mea_w = im_stack.shape
    assert mea_h == mea_w

    offsets = [YX(0, 0)]
    primary = im_stack[0]
    for im in im_stack[1:]:

        # TODO: This could be optimized by using fft instead of
        #       cv2.filter2D() which would avoid the fft of the
        #       unchanging primary.
        conv = convolve(src=primary, kernel=im)

        # conv is now zero-centered; that is, the peak is
        # an offset relative to the center of the image.

        if bounds is not None:
            edge_fill(conv, (mea_w - 2 * bounds) // 2, val=0)

        peak = YX(np.unravel_index(conv.argmax(), conv.shape))
        center = HW(conv.shape) // 2
        offsets += [center - peak]

    if return_shifted_ims:
        raw_dim = im_stack.shape[-2:]
        roi = intersection_roi_from_aln_offsets(offsets, raw_dim)
        roi_dim = (roi[0].stop - roi[0].start, roi[1].stop - roi[1].start)

        pixel_aligned_cy_ims = np.zeros((n_cycles, mea_h, mea_w))
        for cy_i, offset in zip(range(n_cycles), offsets):
            shifted_im = shift(im_stack[cy_i], offset * -1)
            pixel_aligned_cy_ims[cy_i, 0:roi_dim[0],
                                 0:roi_dim[1]] = shifted_im[roi[0], roi[1]]
        return np.array(offsets), pixel_aligned_cy_ims

    else:
        return np.array(offsets)
示例#11
0
def shift(src, loc=XY(0, 0)):
    """Offset"""
    loc = YX(loc)
    extra_dims = src.shape[0:-2]
    n_extra_dims = len(extra_dims)
    src_dim = HW(src.shape[-2:])
    tar_dim = src_dim
    tar_roi, src_roi = clip2d(loc.x, tar_dim.w, src_dim.w, loc.y, tar_dim.h, src_dim.h)
    tar = np.zeros((*extra_dims, *tar_dim))
    if tar_roi is not None and src_roi is not None:
        if n_extra_dims > 0:
            tar[:, tar_roi[0], tar_roi[1]] += src[:, src_roi[0], src_roi[1]]
        else:
            tar[tar_roi[0], tar_roi[1]] += src[src_roi[0], src_roi[1]]
    return tar
示例#12
0
    def it_finds_one_peak_sub_pixel_exactly_under_ideal_conditions():
        true_locs = np.array([[17.5, 17.5]])
        peak_im = imops.gauss2_rho_form(
            amp=1000.0,
            std_x=1.8,
            std_y=1.8,
            pos_x=true_locs[0, 1],
            pos_y=true_locs[0, 0],
            rho=0.0,
            const=0.0,
            mea=35,
        )

        pred_locs = peak_find._pixel_to_subpixel_one_im(
            peak_im, HW(11, 11), true_locs.astype(int)
        )
        dists = np.linalg.norm(true_locs - pred_locs, axis=1)
        assert np.all(dists < 0.001)
示例#13
0
def _quality(im):
    """
    Measure the quality of an image by spatial low-pass filter.
    High quality images are one where there is very little low-frequency
    (but above DC) bands.
    """
    a = np.copy(im)
    a -= np.mean(a)
    power = np.abs(np.fft.fftshift(np.fft.fft2(a)))
    power[power == 0] = 1

    cen = YX(power.shape) / 2
    dim_half = 3
    dim = HW(dim_half * 2 + 1, dim_half * 2 + 1)
    roi = ROI(cen, dim, center=True)
    im = power[roi]

    eigen = imops.eigen_moments(im)
    score = power.sum() / np.sqrt(eigen.sum())
    return score
示例#14
0
    def it_finds_one_peak_sub_pixel_exactly_under_ideal_conditions_many_offsets():
        for trials in range(50):
            true_locs = np.random.uniform(-5, 5, size=(1, 2))
            true_locs += 35 / 2
            peak_im = imops.gauss2_rho_form(
                amp=1000.0,
                std_x=1.8,
                std_y=1.8,
                pos_x=true_locs[0, 1],
                pos_y=true_locs[0, 0],
                rho=0.0,
                const=0.0,
                mea=35,
            )

            pred_locs = peak_find._pixel_to_subpixel_one_im(
                peak_im, HW(11, 11), true_locs.astype(int)
            )
            dists = np.linalg.norm(true_locs - pred_locs, axis=1)
            assert np.all(dists < 0.001)
示例#15
0
def peak_find_chcy_ims_fast(chcy_ims, approx_psf, cycle_i, subpixel=True):
    """
    Unlike the above this assumes that channel balance is working well
    and that we can just mean over the channels
    """

    n_channels = chcy_ims.shape[0]

    np.save(f"/erisyon/internal/_chcy_ims_{cycle_i}.npy", chcy_ims[:, cycle_i,
                                                                   0, 0])
    im = np.mean(chcy_ims[:, cycle_i, :, :], axis=0)
    np.save(f"/erisyon/internal/_mean_im_{cycle_i}.npy", im)
    try:
        locs = pixel_peak_find_one_im(im, approx_psf)
        if subpixel:
            locs = _pixel_to_subpixel_one_im(im, HW(approx_psf.shape),
                                             locs.astype(int))
    except Exception:
        # Failure during peak find, no peaks recorded for this frame.
        locs = np.zeros((0, 2))

    return locs
示例#16
0
    def it_finds_sub_pixel_well_under_typical_conditions():
        bg_std = 10
        with synth.Synth(dim=(512, 512), n_cycles=3) as s:
            true_n_peaks = 100
            peaks = (
                synth.PeaksModelGaussianCircular(n_peaks=true_n_peaks)
                .amps_constant(1000)
                .widths_uniform(1.5)
                .locs_randomize_away_from_edges()
            )
            synth.CameraModel(0, bg_std)
            s.zero_aln_offsets()
            chcy_ims = s.render_chcy()

        chcy_mean_im = np.mean(chcy_ims, axis=(0, 1))
        locs = peak_find._pixel_to_subpixel_one_im(
            chcy_mean_im, HW(peaks.mea, peaks.mea), peaks.locs.astype(int)
        )
        dists = np.linalg.norm(locs - peaks.locs, axis=1)

        assert (dists < 0.1).sum() > 30
        assert (dists < 0.2).sum() > 70
示例#17
0
def low_frequency_power(im, dim_half=3):
    """
    Measure the low_frequency_power (excluding DC) of an image
    by spatial low-pass filter.

    dim_half is the half the width of the region
    """
    a = np.copy(im)
    a -= np.mean(a)
    power = np.abs(np.fft.fftshift(np.fft.fft2(a)))
    power[power == 0] = 1

    cen = YX(power.shape) / 2

    dim = HW(dim_half * 2 + 1, dim_half * 2 + 1)

    # PLUCK out the center (which is the low frequencies)
    roi = ROI(cen, dim, center=True)
    im = power[roi]
    eigen = eigen_moments(im)
    score = power.sum() / np.sqrt(eigen.sum())
    return score
示例#18
0
def intersection_roi_from_aln_offsets(aln_offsets, raw_dim):
    """
    Compute the ROI that contains pixels from all frames
    given the aln_offsets (returned from align)
    and the dim of the original images.
    """
    aln_offsets = np.array(aln_offsets)
    check.affirm(np.all(aln_offsets[0] == (0, 0)),
                 "intersection roi must start with (0,0)")

    # intersection_roi is the ROI in the coordinate space of
    # the [0] frame that has pixels from every cycle.
    clip_dim = (
        np.min(aln_offsets[:, 0] + raw_dim[0]) - np.max(aln_offsets[:, 0]),
        np.min(aln_offsets[:, 1] + raw_dim[1]) - np.max(aln_offsets[:, 1]),
    )

    b = max(0, -np.min(aln_offsets[:, 0]))
    t = min(raw_dim[0], b + clip_dim[0])
    l = max(0, -np.min(aln_offsets[:, 1]))
    r = min(raw_dim[1], l + clip_dim[1])
    return ROI(loc=YX(b, l), dim=HW(t - b, r - l))
示例#19
0
def align(im_stack):
    """
    Align the stack relative to the first frame.
    I timed this versus a non-DFT solution and it was WAY better.

    Returns:
        list of YX tuples
        max_score
    """
    check.array_t(im_stack, ndim=3)
    offsets = [YX(0, 0)]
    maxs = [0]
    primary = im_stack[0]
    for im in im_stack[1:]:
        conv = convolve(src=primary, kernel=im)

        # conv is now zero-centered; that is, the peak is
        # an offset relative to the center of the image.
        maxs += [np.amax(conv)]
        peak = YX(np.unravel_index(conv.argmax(), conv.shape))
        center = HW(conv.shape) // 2
        offsets += [center - peak]

    return np.array(offsets), np.array(maxs)
示例#20
0
def crop(src, off=XY(0, 0), dim=WH(-1, -1), center=False):
    if dim.h == -1 and dim.w == -1:
        dim = HW(src.shape)
    return src[ROI(off, dim, center=center)]
示例#21
0
def _step_3_composite_aligned_images(
    field_df, ch_merged_cy_ims, raw_chcy_ims, sigproc_params
):
    """
    Generate aligned images and composites
    """
    n_outchannels, n_inchannels, n_cycles, dim = sigproc_params.channels_cycles_dim

    # Note offsets are the same for each channel, and we only want one set of
    # offsets because we're aligning channel-merged images.
    offsets = [
        XY(row.shift_x, row.shift_y)
        for row in field_df[field_df.channel_i == 0]
        .set_index("cycle_i")
        .sort_index()[["shift_y", "shift_x"]]
        .itertuples()
    ]
    # Needs to be a list of Coords

    median_by_ch_cy = (
        field_df.set_index(["channel_i", "cycle_i"])
        .sort_index()
        .bg_median.values.reshape((n_outchannels, n_cycles))
    )

    chcy_composite_im, border_size = imops.composite(
        ch_merged_cy_ims,
        offsets,
        start_accum=sigproc_params.peak_find_start,
        limit_accum=sigproc_params.peak_find_n_cycles,
    )

    # GENERATE aligned images in the new coordinate system
    aligned_dim = HW(chcy_composite_im.shape)
    aligned_ims = np.zeros((n_outchannels, n_cycles, aligned_dim.h, aligned_dim.w,))
    aligned_raw_chcy_ims = np.zeros_like(aligned_ims)
    border = YX(border_size, border_size)
    for outch in range(n_outchannels):
        inch = sigproc_params.output_channel_to_input_channel(outch)
        for cy, offset in zip(range(n_cycles), offsets):
            imops.accum_inplace(
                aligned_raw_chcy_ims[outch, cy],
                src=raw_chcy_ims[inch, cy],
                loc=border - offset,
            )
            imops.accum_inplace(
                aligned_ims[outch, cy],
                src=(raw_chcy_ims[inch, cy] - median_by_ch_cy[outch, cy]).clip(min=0),
                loc=border - offset,
            )

    # BLACK out the borders by clipping in only pixels that are in every cycle
    l = border_size - field_df.shift_x.min()
    r = aligned_dim.w - border_size - field_df.shift_x.max()
    b = border_size - field_df.shift_y.min()
    t = aligned_dim.h - border_size - field_df.shift_y.max()
    roi = _roi_from_edges(b, t, l, r)
    aligned_roi_rect = Rect(b, t, l, r)

    aligned_composite_chcy_im = np.zeros(aligned_dim)
    aligned_composite_chcy_im[roi] = chcy_composite_im[roi]
    med = np.median(chcy_composite_im[roi])
    aligned_composite_bg_removed_im = (aligned_composite_chcy_im - med).clip(min=0)

    return (
        border_size,
        aligned_roi_rect,
        aligned_composite_bg_removed_im,
        aligned_raw_chcy_ims,
    )
示例#22
0
 def it_accepts_hw():
     dim = HW(3, 2)
     assert dim.x == 2 and dim.y == 3 and dim == (3, 2)
示例#23
0
文件: psf.py 项目: erisyon/plaster
def _psf_accumulate(im,
                    locs,
                    mea,
                    keep_dist=8,
                    threshold_abs=None,
                    return_reasons=True):
    """
    Given a single im, typically a regional sub-image, accumulate
    PSF evidence from each locs that meets a set of criteria.

    Any one image may not produce enough (or any) candidate spots and it
    is therefore expected that this function is called over a large number
    of fields to get sufficient samples.

    Arguments:
        im: Expected to be a single field, channel, cycle (BG already removed).
        locs: array (n, 2) in coordinates of im. Expected to be well-separated
        mea: The peak_measure (must be odd)
        threshold_abs: The average pixel brightness to accept the peak
        keep_dist: Pixels distance to determine crowding

    Returns:
        psf: ndarray (mea, mea) image
        reason_counts: An array of masks of why peaks were accepted/rejected
            See PSFEstimateMaskFields for the columns
    """
    from scipy.spatial.distance import cdist  # Defer slow import

    n_locs = len(locs)
    dist = cdist(locs, locs, metric="euclidean")
    dist[dist == 0.0] = np.nan

    if not np.all(np.isnan(dist)):
        closest_dist = np.nanmin(dist, axis=1)
    else:
        closest_dist = np.zeros(n_locs)

    # Aligned peaks will accumulate into this psf matrix
    dim = (mea, mea)
    dim2 = (mea + 2, mea + 2)
    psf = np.zeros(dim)

    n_reason_mask_fields = len(PSFEstimateMaskFields)
    reason_masks = np.zeros((n_locs, n_reason_mask_fields))

    for i, (loc, closest_neighbor_dist) in enumerate(zip(locs, closest_dist)):
        reason_masks[i, PSFEstimateMaskFields.considered] = 1

        # EXTRACT a peak with extra pixels around the edges (dim2 not dim)
        peak_im = imops.crop(im, off=YX(loc), dim=HW(dim2), center=True)

        if peak_im.shape != dim2:
            # Skip near edges
            reason_masks[i, PSFEstimateMaskFields.skipped_near_edges] = 1
            continue

        if closest_neighbor_dist < keep_dist:
            reason_masks[i, PSFEstimateMaskFields.skipped_too_crowded] = 1
            continue

        if np.any(np.isnan(peak_im)):
            reason_masks[i, PSFEstimateMaskFields.skipped_has_nan] = 1
            continue

        # Sub-pixel align the peak to the center
        assert not np.any(np.isnan(peak_im))
        centered_peak_im = sub_pixel_center(peak_im.astype(np.float64))

        # Removing ckipping as the noise should cancel out
        # centered_peak_im = np.clip(centered_peak_im, a_min=0.0, a_max=None)
        peak_max = np.max(centered_peak_im)
        if peak_max == 0.0:
            reason_masks[i, PSFEstimateMaskFields.skipped_empty] = 1
            continue

        if threshold_abs is not None and peak_max < threshold_abs:
            # Reject spots that are not active
            reason_masks[i, PSFEstimateMaskFields.skipped_too_dark] = 1
            continue

        r = imops.distribution_aspect_ratio(centered_peak_im)
        if r > 2.0:
            reason_masks[i, PSFEstimateMaskFields.skipped_too_oval] = 1
            continue

        # TRIM off the extra now
        centered_peak_im = centered_peak_im[1:-1, 1:-1]

        psf += centered_peak_im / np.sum(centered_peak_im)
        reason_masks[i, PSFEstimateMaskFields.accepted] = 1

    n_accepted = np.sum(reason_masks[:, PSFEstimateMaskFields.accepted])
    if n_accepted > 0:
        psf /= np.sum(psf)

    if return_reasons:
        return psf, reason_masks

    return psf
示例#24
0
    def __init__(
        self,
        n_peaks=1,  # Number of peaks to add
        n_cycles=1,  # Number of frames to make in the stack
        n_channels=1,  # Number of channels.
        dim=(512, 512),  # dims of frames
        bg_bias=0,  # bias of the background
        bg_std=0.5,  # std of the background noise per channel
        peak_focus=1.0,  # The std of the peaks (all will be the same)
        peak_mean=(
            40.0, ),  # Mean of the peak area distribution for each channel
        peak_std=2.0,  # Std of the peak areas distribution
        peak_xs=None,  # Used to put peaks at know locations
        peak_ys=None,  # Used to put peaks at know locations
        peak_area_all_cycles=None,  # Areas for all peaks on each cycle (len == n_cycles)
        peak_area_by_channel_cycle=None,  # Areas, all peaks, channels, cycles (len ==  n_channels * n_cycles * n_peaks)
        grid_distribution=False,  # When True the peaks are laid out in a grid
        all_aligned=False,  # When True all cycles will be aligned
        digitize=False,
        frame_offsets=None,
        anomalies=None,  # int, number of anomalies per image
        random_seed=None,
    ):

        if random_seed is not None:
            np.random.seed(random_seed)

        if not isinstance(bg_bias, tuple):
            bg_bias = tuple([bg_bias] * n_channels)

        if not isinstance(bg_std, tuple):
            bg_std = tuple([bg_std] * n_channels)

        self.bg_bias = bg_bias
        assert len(bg_bias) == n_channels

        self.bg_std = bg_std
        assert len(bg_std) == n_channels

        self.dim = HW(dim)

        self.anomalies = []

        # Peaks are a floating point positions (not perfectly aligned with pixels)
        # and the Gaussians are sampled around those points

        self.n_peaks = n_peaks
        self.n_cycles = n_cycles
        self.n_channels = n_channels
        self.peak_mean = peak_mean
        self.peak_std = peak_std
        self.peak_focus = peak_focus

        # unit_peak is only a reference; the real peaks are sub-pixel sampled but will have the same dimensions
        self.unit_peak = imops.generate_gauss_kernel(self.peak_focus)

        self.peak_dim = self.unit_peak.shape[0]

        if peak_xs is not None and peak_ys is not None:
            # Place peaks at specific locations
            self.peak_xs = np.array(peak_xs)
            self.peak_ys = np.array(peak_ys)
        else:
            # Place peaks in patterns or random
            if grid_distribution:
                # Put the peaks on a grid
                n_cols = int(math.sqrt(n_peaks) + 0.5)
                ixs = np.remainder(np.arange(n_peaks), n_cols)
                iys = np.arange(n_peaks) // n_cols
                border = 15
                self.peak_xs = (self.dim.w -
                                border * 2) * ixs / n_cols + border
                self.peak_ys = (self.dim.h -
                                border * 2) * iys / n_cols + border
            else:
                # Distribute the peaks randomly
                self.peak_xs = np.random.uniform(
                    low=self.peak_dim + 1.0,
                    high=self.dim.w - self.peak_dim - 1.0,
                    size=n_peaks,
                )
                self.peak_ys = np.random.uniform(
                    low=self.peak_dim + 1.0,
                    high=self.dim.h - self.peak_dim - 1.0,
                    size=n_peaks,
                )

        self.peak_locs = [XY(x, y) for x, y in zip(self.peak_xs, self.peak_ys)]
        if self.n_peaks > 0:
            peak_dists = distance.cdist(self.peak_locs, self.peak_locs,
                                        "euclidean")
            peak_dists[peak_dists == 0] = 10000.0
            self.closest = peak_dists.min(axis=0)

        self.peak_areas = np.empty((n_channels, n_cycles, n_peaks))
        if peak_area_all_cycles is not None:
            # Use one area for all peaks, all channels for each cycle
            assert len(peak_area_all_cycles) == n_cycles
            for cycle in range(n_cycles):
                self.peak_areas[:, cycle, :] = peak_area_all_cycles[cycle]
        elif peak_area_by_channel_cycle is not None:
            # Specified areas for each peak, each channel, each cycle
            assert peak_area_by_channel_cycle.shape == (n_channels, n_cycles,
                                                        n_peaks)
            self.peak_areas[:] = peak_area_by_channel_cycle[:]
        elif n_peaks > 0:
            # Make random peak areas by channel means
            for channel in range(n_channels):
                self.peak_areas[channel, :, :] = np.random.normal(
                    loc=self.peak_mean[channel],
                    scale=self.peak_std,
                    size=(n_cycles, n_peaks),
                )
                self.peak_areas[channel] = np.clip(self.peak_areas[channel],
                                                   0.0, 1000.0)

        # Frames are integer aligned because this is the best that the aligner would be able to do
        if frame_offsets is None:
            self.frame_xs = np.random.randint(low=-5, high=5, size=n_cycles)
            self.frame_ys = np.random.randint(low=-5, high=5, size=n_cycles)
        else:
            self.frame_xs = [i[1] for i in frame_offsets]
            self.frame_ys = [i[0] for i in frame_offsets]

        # Cycle 0 always has no offset
        self.frame_xs[0] = 0
        self.frame_ys[0] = 0

        if all_aligned:
            self.frame_xs = np.zeros((n_cycles, ))
            self.frame_ys = np.zeros((n_cycles, ))

        self.ims = np.zeros((n_channels, n_cycles, dim[0], dim[1]))
        for cycle in range(n_cycles):
            for channel in range(n_channels):
                # Background has bg_std plus bias
                im = bg_bias[channel] + np.random.normal(
                    size=dim, scale=self.bg_std[channel])

                # Peaks are on the pixel lattice from their floating point positions
                # No signal-proportional noise is added
                for x, y, a in zip(self.peak_xs, self.peak_ys,
                                   self.peak_areas[channel, cycle, :]):
                    # The center of the peak is at the floor pixel and the offset is the fractional part
                    _x = self.frame_xs[cycle] + x
                    _y = self.frame_ys[cycle] + y
                    ix = int(_x + 0.5)
                    iy = int(_y + 0.5)
                    frac_x = _x - ix
                    frac_y = _y - iy
                    g = imops.generate_gauss_kernel(self.peak_focus,
                                                    offset_x=frac_x,
                                                    offset_y=frac_y)
                    imops.accum_inplace(im, g * a, loc=XY(ix, iy), center=True)

                # overwrite with random anomalies if specified
                if anomalies is not None:
                    if cycle == 0:
                        # in cycle 0 pick location and size of anomalies for this image
                        anomalies_for_channel = []
                        self.anomalies += [anomalies_for_channel]
                        for i in range(anomalies):
                            sz = np.random.randint(10, 100, size=2)
                            l = np.random.randint(0, self.dim[0], size=2)
                            anomalies_for_channel += [(l, sz)]
                    for l, sz in self.anomalies[channel]:
                        # in all cycles, vary the location, size, and intensity of the anomaly,
                        # and print it to the image.
                        l = l * (1.0 + np.random.random() / 10.0)
                        sz = sz * (1.0 + np.random.random() / 10.0)
                        im[ROI(
                            l,
                            sz)] = im[ROI(l, sz)] * np.random.randint(2, 20)

                if digitize:
                    im = (im.clip(min=0) + 0.5).astype(
                        np.uint8)  # +0.5 to round up
                else:
                    im = im.clip(min=0)
                self.ims[channel, cycle, :, :] = im
示例#25
0
def df_filter(
    df,
    fields=None,
    reject_fields=None,
    roi=None,
    channel_i=0,
    dark=None,
    on_through_cy_i=None,
    off_at_cy_i=None,
    monotonic=None,
    min_intensity_cy_0=None,
    max_intensity_cy_0=None,
    max_intensity_any_cycle=None,
    min_intensity_per_cycle=None,
    max_intensity_per_cycle=None,
    radmat_field="signal",
    max_k=None,
    min_score=None,
):
    """
    A general filtering tool that operates on the dataframe returned by
    sigproc_v2.fields__n_peaks__peaks__radmat()
    """
    n_channels = df.channel_i.max() + 1

    # REMOVE unwanted fields
    if fields is None:
        fields = list(range(df.field_i.max() + 1))
    if reject_fields is not None:
        fields = list(filter(lambda x: x not in reject_fields, fields))
    _df = df[df.field_i.isin(fields)].reset_index(drop=True)

    # REMOVE unwanted peaks by ROI
    if roi is None:
        roi = ROI(YX(0, 0), HW(df.raw_y.max(), df.raw_x.max()))
    _df = _df[(roi[0].start <= _df.raw_y)
              & (_df.raw_y < roi[0].stop)
              & (roi[1].start <= _df.raw_x)
              & (_df.raw_x < roi[1].stop)].reset_index(drop=True)

    if max_k is not None:
        _df = _df[_df.k <= max_k]

    if min_score is not None:
        _df = _df[_df.score >= min_score]

    # OPERATE on radmat if needed
    fields_that_operate_on_radmat = [
        dark,
        on_through_cy_i,
        off_at_cy_i,
        monotonic,
        min_intensity_cy_0,
        max_intensity_cy_0,
        max_intensity_any_cycle,
        min_intensity_per_cycle,
        max_intensity_per_cycle,
    ]

    if any([field is not None for field in fields_that_operate_on_radmat]):
        assert 0 <= channel_i < n_channels

        rad_pt = pd.pivot_table(_df,
                                values=radmat_field,
                                index=["peak_i"],
                                columns=["channel_i", "cycle_i"])
        ch_rad_pt = rad_pt.loc[:, channel_i]
        keep_peaks_mask = np.ones((ch_rad_pt.shape[0], ), dtype=bool)

        if on_through_cy_i is not None:
            assert dark is not None
            keep_peaks_mask &= np.all(
                ch_rad_pt.loc[:, 0:on_through_cy_i + 1] > dark, axis=1)

        if off_at_cy_i is not None:
            assert dark is not None
            keep_peaks_mask &= np.all(ch_rad_pt.loc[:, off_at_cy_i:] < dark,
                                      axis=1)

        if monotonic is not None:
            d = np.diff(ch_rad_pt.values, axis=1)
            keep_peaks_mask &= np.all(d < monotonic, axis=1)

        if min_intensity_cy_0 is not None:
            keep_peaks_mask &= ch_rad_pt.loc[:, 0] >= min_intensity_cy_0

        if max_intensity_cy_0 is not None:
            keep_peaks_mask &= ch_rad_pt.loc[:, 0] <= max_intensity_cy_0

        if max_intensity_any_cycle is not None:
            keep_peaks_mask &= np.all(
                ch_rad_pt.loc[:, :] <= max_intensity_any_cycle, axis=1)

        if min_intensity_per_cycle is not None:
            for cy_i, inten in enumerate(min_intensity_per_cycle):
                if inten is not None:
                    keep_peaks_mask &= ch_rad_pt.loc[:, cy_i] >= inten

        if max_intensity_per_cycle is not None:
            for cy_i, inten in enumerate(max_intensity_per_cycle):
                if inten is not None:
                    keep_peaks_mask &= ch_rad_pt.loc[:, cy_i] <= inten

        keep_peak_i = ch_rad_pt[keep_peaks_mask].index.values
        keep_df = pd.DataFrame(
            dict(keep_peak_i=keep_peak_i)).set_index("keep_peak_i")
        _df = keep_df.join(df.set_index("peak_i", drop=False))

    return _df
示例#26
0
def _roi_from_edges(b, t, l, r):
    return ROI(loc=YX(b, l), dim=HW(t - b, r - l))
示例#27
0
    def _im_setup(self, im, fig):
        from bokeh.models import LinearColorMapper  # Defer slow imports

        _im = im
        assert _im.ndim == 2

        ustack = self._u_stack()

        y = 0
        if ustack.get("_flip_y"):
            assert "f_y_range" not in ustack
            _im = np.flip(_im, axis=0)
            y = _im.shape[0]

        dim = HW(_im.shape)

        if dim.w == 0 or dim.h == 0:
            # Deal with zero dims gracefully
            dim = HW(max(1, dim.h), max(1, dim.w))
            _im = np.zeros(dim)

        full_w, full_h = (ustack.get("_full_w", False), ustack.get("_full_h", False))
        if ustack.get("_full"):
            full_w, full_h = (True, True)

        if full_h:
            full_h = dim.h + 40  # TASK: This value is weird, need a way to derive it
            if ustack.get("_min_h") is not None:
                full_h = max(full_h, ustack.get("_min_h"))

        if full_w:
            full_w = dim.w + 20  # TASK: This value is weird, need a way to derive it
            if ustack.get("_min_w") is not None:
                full_w = max(full_w, ustack.get("_min_w"))

        _nan = ustack.get("_nan")
        if _nan is not None:
            _im = np.nan_to_num(_im)

        _cper = ustack.get("_cper")
        if _cper is not None:
            # color by percentile
            assert isinstance(_cper, tuple) and len(_cper) == 2
            low, high = np.nanpercentile(_im, _cper)
        else:
            _cspan = ustack.get("_cspan")
            if _cspan is None:
                low = 0.0
                if _im.shape[0] == 0:
                    high = 1.0
                else:
                    with warnings.catch_warnings():
                        warnings.filterwarnings("error")
                        try:
                            high = np.nanmean(_im) + np.nanstd(_im) * 4
                        except Exception:
                            high = 1.0
            else:
                if isinstance(_cspan, (list, tuple)):
                    assert len(_cspan) == 2
                    low = _cspan[0]
                    high = _cspan[1]
                elif isinstance(_cspan, np.ndarray):
                    assert _cspan.shape == (2,)
                    low = _cspan[0]
                    high = _cspan[1]
                else:
                    assert isinstance(_cspan, (int, float))
                    low = 0
                    high = _cspan

        pal = ustack.get("_palette", "viridis")
        if pal == "viridis":
            from bokeh.palettes import viridis

            pal = viridis(256)

        elif pal in ("gray", "grey",):
            from bokeh.palettes import gray

            pal = gray(256)

        elif pal == "inferno":
            from bokeh.palettes import inferno

            pal = inferno(256)

        # zero_color = ustack.get("_zero_color")
        # if zero_color is not None:
        #     pal = copy.copy(pal)
        #     pal[0] = zero_color

        cmap = LinearColorMapper(palette=pal, low=low, high=high)

        if full_w:
            fig.plot_width = full_w

        if full_h:
            fig.plot_height = full_h

        return dim, cmap, _im, y
示例#28
0
 def it_enlarged_the_canvas_to_maximum_offset():
     hw = HW(comp.shape)
     assert (
         hw.w == 62 and hw.h == 62
     )  # 62 because the max offset is 6. So 6*2 (each side) + 50 original dim
示例#29
0
 def it_shifts_an_roi():
     roi = ROI(loc=YX(5, 10), dim=HW(15, 20))
     new_roi = roi_shift(roi, YX(-2, -3))
     assert new_roi == ROI(YX(5 - 2, 10 - 3), HW(15, 20))
示例#30
0
def _radiometry(chcy_ims, locs, ch_z_reg_psfs, cycle_to_z_index):
    """
    Use the PSFs to compute the Area-Under-Curve of the data in chcy_ims
    for each peak location of locs.

    Arguments:
        chcy_ims: (n_output_channels, n_cycles, width, height)
        locs: (n_peaks, 2). The second dimension is in (y, x) order
        ch_z_reg_psfs: (n_output_channels, n_z_slices, divs, divs, psf_mea, psf_mea)
        cycle_to_z_index: (n_cycles).
            This is the best z-slice of the ch_z_reg_psfs to use for
            each cycle determined by a focal fit.
    """
    check.array_t(chcy_ims, ndim=4)
    check.array_t(locs, ndim=2, shape=(None, 2))
    check.array_t(ch_z_reg_psfs,
                  shape=(chcy_ims.shape[0], None, None, None, None, None))
    check.array_t(cycle_to_z_index, shape=(chcy_ims.shape[1], ))

    n_locs = len(locs)
    n_channels, n_cycles = chcy_ims.shape[0:2]
    psf_divs = ch_z_reg_psfs.shape[2]
    assert psf_divs == ch_z_reg_psfs.shape[3]
    psf_dim = ch_z_reg_psfs.shape[-2:]
    psf_mea = psf_dim[0]
    assert psf_mea == psf_dim[1]

    radmat = np.full((n_locs, n_channels, n_cycles, 2),
                     np.nan)  # 2 is (sig, noi)

    center_weighted_mask = imops.generate_center_weighted_tanh(psf_mea,
                                                               radius=2.0)

    for ch_i in range(n_channels):
        for cy_i in range(n_cycles):
            reg_psfs = ch_z_reg_psfs[ch_i, cycle_to_z_index[cy_i]]

            im = chcy_ims[ch_i, cy_i]

            for loc_i, loc in enumerate(locs):
                peak_im = imops.crop(im,
                                     off=YX(loc),
                                     dim=HW(psf_dim),
                                     center=True)
                if peak_im.shape != psf_dim:
                    # Skip near edges
                    continue

                if np.any(np.isnan(peak_im)):
                    # Skip nan collisions
                    continue

                # There is a small issue here -- when the regional PSFs
                # are computed they divide up the image over the full width
                # but the locs here are actually referring to the aligned
                # space which is typically a little smaller. This might
                # cause problems if alignment is very poor but is probably
                # too small of an effect to worry about in typical operations.
                psf_kernel = reg_psfs[int(psf_divs * loc[0] / im.shape[0]),
                                      int(psf_divs * loc[1] / im.shape[1]), ]

                signal, noise = _peak_radiometry(
                    peak_im,
                    psf_kernel,
                    center_weighted_mask=center_weighted_mask)
                radmat[loc_i, ch_i, cy_i, :] = (signal, noise)

    return radmat