Example #1
0
 def it_clips2d_inclusive():
     """
     ssssssssssss
     sttttttttsss
     sttttttttsss
     ssssssssssss
     """
     tar_roi, src_roi = tools.image.coord.clip2d(-1, 5, 10, -2, 4, 15)
     assert tar_roi == ROI(XY(0, 0), WH(5, 4))
     assert src_roi == ROI(XY(1, 2), WH(5, 4))
Example #2
0
 def it_extracts_a_trace():
     im_times_10 = im2 * 10
     trace = imops.extract_trace(
         [im2, im_times_10], loc=XY(1, 0), dim=WH(2, 2), center=False
     )
     expected = [[[2, 3], [5, 6]], [[20, 30], [50, 60]]]
     assert np.array_equal(trace, expected)
Example #3
0
 def render(self, im, fl_i, ch_i, cy_i, aln_offset):
     super().render(im, fl_i, ch_i, cy_i, aln_offset)
     size = int(self.std * 2.5)
     size += 1 if size % 2 == 0 else 0
     bg_mean = np.median(im) - 1
     blur = cv2.GaussianBlur(im, (size, size), self.std) - bg_mean - 1
     imops.accum_inplace(im, self.scale * blur, XY(0, 0), center=False)
Example #4
0
 def it_adds_a_sub_image_into_a_target():
     dst = np.ones(WH(4, 4))
     src = np.ones(WH(2, 2))
     imops.accum_inplace(dst, src, XY(1, 1))
     good = np.array([[1, 1, 1, 1], [1, 2, 2, 1], [1, 2, 2, 1],
                      [1, 1, 1, 1]])
     assert (dst == good).all()
Example #5
0
    def render(self, im, fl_i, ch_i, cy_i, aln_offset):
        super().render(im, fl_i, ch_i, cy_i, aln_offset)

        blob = imops.generate_circle_mask(self.size, size=self.size * 3)
        imops.accum_inplace(
            im, self.amp * blob, XY(0.25 * im.shape[0], 0.25 * im.shape[0]), center=True
        )
Example #6
0
def spotty_images():
    # CREATE a test spot
    spot = imops.generate_gauss_kernel(2.0)
    spot = spot / np.max(spot)

    dim = WH(50, 50)
    spot_locs = [XY(15, 15), XY(10, 20), XY(20, 21)]

    # CREATE test images with spots
    test_images = []
    for loc in spot_locs:
        im = np.zeros(dim)
        # im = np.random.normal(0, 0.1, dim)
        # im = np.ones(dim) * 0.1
        imops.accum_inplace(im, spot, loc=loc, center=True)
        test_images += [im]

    return spot_locs, np.array(test_images)
Example #7
0
def _mask_anomalies_im(im, den_threshold=300):
    """
    Operates on pre-balanced images.
    The den_threshold of 300 was found empirically on Val data

    Sets anomalies to nan
    """
    import skimage.transform  # Defer slow imports
    import cv2

    check.array_t(im, is_square=True)

    # SLICE into square using numpy-foo by reshaping the image
    # into a four-dimensional array can then by np.mean on the inner dimensions.
    sub_mea = 4  # Size of the sub-sample region
    im_mea, _ = im.shape

    squares = im.reshape(im_mea // sub_mea, sub_mea, im_mea // sub_mea,
                         sub_mea)
    # At this point, im is now 4-dimensional like: (256, 2, 256, 2)
    # But we want the small_dims next to each other for simplicity so swap the inner axes
    squares = squares.swapaxes(1, 2)
    # Now squares is (256, 256, 2, 2.)

    # squares is like: 256, 256, 2, 2. So we need the mean of the last two axes
    squares = np.mean(squares, axis=(2, 3))

    bad_mask = (squares > den_threshold).astype(float)

    # EXPAND the bad areas by erosion and dilate.
    # Erosion gets rid of the single-pixel hits and dilation expands the bad areas
    kernel = np.ones((3, 3), np.uint8)
    mask = cv2.erode(bad_mask, kernel, iterations=1)
    mask = cv2.dilate(mask, kernel, iterations=3)

    scale = im.shape[0] // mask.shape[0]

    full_size_mask = skimage.transform.rescale(
        mask,
        scale=scale,
        multichannel=False,
        mode="constant",
        anti_aliasing=False).astype(bool)

    # FIND rect contours of bad areas
    contours, hierarchy = cv2.findContours(full_size_mask.astype("uint8"),
                                           cv2.RETR_LIST,
                                           cv2.CHAIN_APPROX_SIMPLE)
    bad_rects = [cv2.boundingRect(cnt) for cnt in contours]
    im = im.copy()
    for rect in bad_rects:
        imops.fill(im,
                   loc=XY(rect[0], rect[1]),
                   dim=WH(rect[2], rect[3]),
                   val=np.nan)

    return im
Example #8
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
Example #9
0
 def _peak(into_im, x, y, rho=0.0, mea=15, amp=1000.0, peak_sigma=1.8):
     corner_y = int(y - mea / 2.0 + 0.5)  # int
     corner_x = int(x - mea / 2.0 + 0.5)
     off_y = y - corner_y  # float
     off_x = x - corner_x
     peak_im = imops.gauss2_rho_form(amp, peak_sigma, peak_sigma, off_x,
                                     off_y, rho, 0.0, mea)
     imops.accum_inplace(into_im,
                         peak_im,
                         loc=XY(corner_x, corner_y),
                         center=False)
Example #10
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
Example #11
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]
Example #12
0
    def it_fills():
        im = np.ones((3, 3))
        im = imops.edge_fill(im, 1)
        # fmt: off
        assert im.tolist() == [
            [0.0, 0.0, 0.0],
            [0.0, 1.0, 0.0],
            [0.0, 0.0, 0.0]
        ]
        # fmt: on

        dst = np.ones(WH(4, 4))
        imops.fill(dst, loc=XY(1, 1), dim=WH(10, 10))
        good = np.array([[1, 1, 1, 1], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]])
        assert (dst == good).all()
Example #13
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
Example #14
0
def _mask_anomalies(cy_ims, bad_rects_by_cycle):
    """
    Given a cycle stack of images and the list of bad rects,
    fill all these rects with background noise so that the aligner
    won't be confused by those anomalies.

    Arguments:
        cy_ims: array (n_cycles, height, width)
        bad_rects_by_cycle: List of bad rects for each cycle

    Returns:
        A copy of cy_ims with the bad rects masked with noise
    """
    assert cy_ims.ndim == 3 and cy_ims.shape[1] == cy_ims.shape[2]

    n_cycles, _, _ = cy_ims.shape

    masked_ims = np.zeros_like(cy_ims)

    for cy in range(n_cycles):
        src_im = cy_ims[cy]
        bad_rects = bad_rects_by_cycle[cy]

        # MAKE a mask_im with 0 inside bad rects, 1 otherwise
        mask_im = np.ones_like(src_im)
        for rect in bad_rects:
            imops.fill(mask_im,
                       loc=XY(rect[0], rect[1]),
                       dim=WH(rect[2], rect[3]),
                       val=0)

        # FIND the characteristics of a normal distribution that fits the
        # data that is not masked out (that is, we don't want the anomalies
        # in this distribution).  If src_im is entirely masked, mean=std=0.
        # TASK: This could be accelerated by subsampling.
        mean = std = 0
        if np.any(mask_im):
            mean, std = norm.fit(src_im[mask_im > 0])

        bg_noise = norm.rvs(loc=mean,
                            scale=std,
                            size=src_im.shape,
                            random_state=None)
        masked_ims[cy] = np.where(mask_im < 1, bg_noise, src_im)

    return masked_ims
Example #15
0
 def it_has_x_y_w_h_properties():
     loc = XY(1, 2)
     assert loc.x == 1 and loc.y == 2 and loc.w == 1 and loc.h == 2
Example #16
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
Example #17
0
 def render(self, im, fl_i, ch_i, cy_i, aln_offset):
     super().render(im, fl_i, ch_i, cy_i, aln_offset)
     bg = np.random.normal(loc=self.bg_mean, scale=self.bg_std, size=self.dim)
     imops.accum_inplace(im, bg, XY(0, 0), center=False)
Example #18
0
def _subsize_sub_pixel_align_cy_ims(pixel_aligned_cy_ims, subsize, n_samples):
    """
    The inner loop of _sub_pixel_align_cy_ims() that executes on a "subsize"
    region of the larger image.

    Is subsize is None then it uses the entire image.
    """
    n_max_failures = n_samples * 2
    sub_pixel_offsets = np.zeros((n_samples, pixel_aligned_cy_ims.shape[0], 2))
    pixel_aligned_cy0_im = pixel_aligned_cy_ims[0]

    im_mea = pixel_aligned_cy_ims.shape[-1]
    assert pixel_aligned_cy_ims.shape[-2] == im_mea

    def _subregion(im, pos):
        if subsize is None:
            return im
        else:
            return imops.crop(im,
                              off=pos,
                              dim=WH(subsize, subsize),
                              center=False)

    sample_i = 0
    n_failures = 0
    while sample_i < n_samples and n_failures < n_max_failures:
        try:
            if subsize is None:
                pos = XY(0, 0)
            else:
                pos = XY(
                    np.random.randint(0, im_mea - subsize - 16),
                    np.random.randint(0, im_mea - subsize - 16),
                )

            subregion_pixel_aligned_cy0_im = _subregion(
                pixel_aligned_cy0_im, pos)

            for cy_i, pixel_aligned_cy_im in enumerate(pixel_aligned_cy_ims):
                if cy_i == 0:
                    continue

                # Use a small region to improve speed
                subregion_pixel_aligned_cy_im = _subregion(
                    pixel_aligned_cy_im, pos)

                try:
                    _dy, _dx = _subpixel_align_one_im(
                        subregion_pixel_aligned_cy0_im,
                        subregion_pixel_aligned_cy_im,
                    )
                    sub_pixel_offsets[sample_i, cy_i, :] = (_dy, _dx)
                except Exception:
                    # This is a general exception handler because there
                    # are a number of ways that the _subpixel_align_one_im
                    # can fail including linear algebera, etc. All
                    # of which end up with a skip and a retry.
                    n_failures += 1
                    raise AlignmentError

            sample_i += 1

        except AlignmentError:
            # Try again with a new pos
            if n_failures >= n_max_failures:
                raise AlignmentError

    return np.mean(sub_pixel_offsets, axis=0)
Example #19
0
def _raw_peak_i_zoom(
        field_i,
        res,
        df,
        peak_i,
        channel=0,
        zoom=3.0,
        square_radius=7,
        x_pad=0,
        cspan=(0, 5_000),
        separate=False,
        show_circles=True,
):
    peak_i = int(peak_i)
    peak_records = df[df.peak_i == peak_i]
    field_i = int(peak_records.iloc[0].field_i)

    im = res.raw_chcy_ims(field_i)
    all_sig = res.sig()

    square = cspan[1] * imops.generate_square_mask(square_radius)

    sig_for_channel = all_sig[peak_i, channel, :]
    sig_top = np.median(sig_for_channel) + np.percentile(sig_for_channel, 99.9)

    height = (square_radius + 1) * 2 + 1
    one_width = height + x_pad
    all_width = one_width * res.n_cycles - x_pad
    im_all_cycles = np.zeros((height, all_width))

    f_plot_height = height * zoom
    f_plot_width = all_width * zoom

    z = ZPlots()
    if show_circles:
        for cycle_i in range(res.n_cycles):
            im_with_marker = np.copy(im[channel, cycle_i])
            cy_rec = peak_records[peak_records.cycle_i == cycle_i].iloc[0]
            loc = XY(cy_rec.raw_x, cy_rec.raw_y)

            imops.accum_inplace(im_with_marker, square, loc=loc, center=True)
            im_with_marker = imops.extract_with_mask(
                im_with_marker,
                imops.generate_square_mask(square_radius + 1, True),
                loc=loc,
                center=True,
            )
            imops.accum_inplace(im_all_cycles,
                                im_with_marker,
                                loc=XY(cycle_i * one_width, 0))

            if separate:
                z.im(
                    im_with_marker,
                    _noaxes=True,
                    f_plot_height=int(f_plot_height),
                    f_plot_width=int(f_plot_height),
                    _notools=True,
                    f_match_aspect=True,
                    _cspan=cspan,
                )

    if not separate:
        z.im(
            im_all_cycles,
            _noaxes=True,
            f_plot_height=int(f_plot_height),
            f_plot_width=int(f_plot_width),
            _notools=True,
            f_match_aspect=True,
            _cspan=cspan,
        )
Example #20
0
    def show_raw(
        peak_i,
        field_i,
        channel_i,
        cycle_i,
        min_bright=min_bright,
        max_bright=max_bright,
        show_circles=show_circles,
    ):
        field_i = int(field_i) if field_i != "" else None
        channel_i = int(channel_i)
        cycle_i = int(cycle_i)
        if field_i is None:
            peak_i = int(peak_i)
            peak_records = df[df.peak_i == peak_i]
            field_i = int(peak_records.iloc[0].field_i)
        else:
            peak_i = None

        all_sig = res.sig()

        # mask_rects_for_field = res.raw_mask_rects_df()[field_i]
        # Temporarily removed. This is going to involve some groupby Super-Pandas-Kungfu(tm)
        # Here is my too-tired start...
        """
        import pandas as pd
        df = pd.DataFrame([
            (0, 0, 0, 100, 110, 120, 130),
            (0, 0, 1, 101, 111, 121, 131),
            (0, 0, 2, 102, 112, 122, 132),
            (0, 1, 0, 200, 210, 220, 230),
            (0, 1, 1, 201, 211, 221, 231),
            (0, 1, 2, 202, 212, 222, 232),
            (1, 0, 0, 1100, 1110, 1120, 1130),
            (1, 0, 1, 1101, 1111, 1121, 1131),
            (1, 0, 2, 1102, 1112, 1122, 1132),
            (1, 1, 0, 1200, 1210, 1220, 1230),
            (1, 1, 1, 1201, 1211, 1221, 1231),
        ], columns=["frame_i", "ch_i", "cy_i", "x", "y", "w", "h"])

        def rec(row):
            return row[["x", "y"]]

        df.set_index("frame_i").groupby(["frame_i"]).apply(rec)
        """
        mask_rects_for_field = None

        cspan = (min_bright, max_bright)
        circle = cspan[1] * imops.generate_donut_mask(4, 3)
        square = cspan[1] * imops.generate_square_mask(square_radius)

        z = ZPlots()
        sig_for_channel = all_sig[:, channel_i, :]
        sig_top = np.median(sig_for_channel) + np.percentile(
            sig_for_channel, 99.9)

        if peak_i is not None:
            rad = sig_for_channel[peak_i]
            rad = rad.reshape(1, rad.shape[0])
            print("\n".join([
                f"    cycle {cycle:2d}: {r:6.0f}"
                for cycle, r in enumerate(rad[0])
            ]))
            z.scat(x=range(len(rad[0])), y=rad[0])
            z.im(rad, _cspan=(0, sig_top), f_plot_height=50, _notools=True)

            # This is inefficient because the function we will call
            # does the same image load, but I'd prefer to not repeat
            # the code here and want to be able to call this fn
            # from notebooks:
            _raw_peak_i_zoom(
                field_i,
                res,
                df,
                peak_i,
                channel_i,
                zoom=3.0,
                square_radius=square_radius,
                x_pad=1,
                cspan=cspan,
                separate=False,
                show_circles=show_circles,
            )

        if result_block == "sigproc_v1":
            im = res.raw_chcy_ims(field_i).copy()[channel_i, cycle_i]
        else:
            im = res.aln_ims[field_i, channel_i, cycle_i].copy()

        if peak_i is not None:
            cy_rec = peak_records[peak_records.cycle_i == cycle_i].iloc[0]
            im_marker = square if peak_i_square else circle
            imops.accum_inplace(
                im,
                im_marker,
                loc=XY(cy_rec.raw_x, cy_rec.raw_y),
                center=True,
            )

        elif show_circles:
            peak_records = df[(df.field_i == field_i)
                              & (df.cycle_i == cycle_i)]

            # In the case of a field with no peaks, n_peaks may be NaN, so check that we have
            # some peaks before passing NaNs to imops.
            if peak_records.n_peaks.iloc[0] > 0:
                for i, peak in peak_records.iterrows():
                    imops.accum_inplace(
                        im,
                        circle,
                        loc=XY(peak.raw_x, peak.raw_y),
                        center=True,
                    )

        z.im(
            im,
            f_title=f"ch_i={channel_i}  cy_i={cycle_i}  fl_i={field_i}",
            _full=True,
            _noaxes=True,
            _cspan=(float(min_bright), float(max_bright)),
        )
        displays.fix_auto_scroll()
Example #21
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)]
Example #22
0
def _step_4_find_peaks(
    aligned_composite_bg_removed_im,
    aligned_roi_rect,
    raw_mask_rects,
    border_size,
    field_df,
    sigproc_params,
):
    """
    Find peaks on the composite image

    TASK: Remove the mask rect checks and replace with the same masking
    logic that is now implemented in the alignment phase. That is, just remove
    the peaks from the source instead of in post-processing.
    """
    from skimage.feature import peak_local_max  # Defer slow import
    from scipy.stats import iqr

    n_outchannels, n_inchannels, n_cycles, dim = sigproc_params.channels_cycles_dim
    assert (
        aligned_composite_bg_removed_im.shape[0]
        == aligned_composite_bg_removed_im.shape[1]
    )
    aligned_dim, _ = aligned_composite_bg_removed_im.shape
    check.array_t(aligned_composite_bg_removed_im, is_square=True)

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

    kernel = imops.generate_gauss_kernel(1.0)
    kernel = kernel - kernel.mean()
    _fiducial_im = imops.convolve(aligned_composite_bg_removed_im, kernel)

    # Black out the convolution artifact around the perimeter of the _fiducial_im
    search_roi_rect = Rect(
        aligned_roi_rect.b + brim_rad,
        aligned_roi_rect.t - brim_rad,
        aligned_roi_rect.l + brim_rad,
        aligned_roi_rect.r - brim_rad,
    )
    search_roi = search_roi_rect.roi()
    composite_fiducial_im = np.zeros_like(aligned_composite_bg_removed_im)

    # Use Inter-Quartile Range for some easy filtering
    _iqr = 0
    if sigproc_params.iqr_rng is not None:
        _iqr = iqr(
            _fiducial_im[search_roi],
            rng=(100 - sigproc_params.iqr_rng, sigproc_params.iqr_rng),
        )

    composite_fiducial_im[search_roi] = (_fiducial_im[search_roi] - _iqr).clip(min=0)

    locs = peak_local_max(
        composite_fiducial_im,
        min_distance=hat_rad,
        threshold_abs=sigproc_params.threshold_abs,
    )

    # Emergency exit to prevent memory overflows
    # check.affirm(len(locs) < 7000, f"Too many peaks {len(locs)}")

    shift = field_df.set_index("cycle_i").sort_index()[["shift_y", "shift_x"]].values
    shift_y = shift[:, 0]
    shift_x = shift[:, 1]

    # Discard any peak in any mask_rect
    # ALIGN the mask rects to the composite coordinate system
    aligned_mask_rects = []
    for channel in range(sigproc_params.n_output_channels):
        channel_rects = safe_list_get(raw_mask_rects, channel, [])
        for cycle in range(n_cycles):
            for rect in safe_list_get(channel_rects, cycle, []):
                yx = XY(rect[0], rect[1])
                hw = WH(rect[2], rect[3])
                yx += XY(border_size, border_size) - XY(shift_x[cycle], shift_y[cycle])
                aligned_mask_rects += [(yx[0], yx[1], yx[0] + hw[0], yx[1] + hw[1])]

    aligned_mask_rects = np.array(aligned_mask_rects)
    if aligned_mask_rects.shape[0] > 0:

        # To compare every loc with every mask rect we use the tricky np.fn.outer()
        y_hits = np.greater_equal.outer(locs[:, 0], aligned_mask_rects[:, 0])
        y_hits &= np.less.outer(locs[:, 0], aligned_mask_rects[:, 2])

        x_hits = np.greater_equal.outer(locs[:, 1], aligned_mask_rects[:, 1])
        x_hits &= np.less.outer(locs[:, 1], aligned_mask_rects[:, 3])

        inside_rect = x_hits & y_hits  # inside a rect if x and y are inside the rect
        locs_to_keep = ~np.any(
            inside_rect, axis=1
        )  # Reject if inside of any masked rect
        locs = locs[locs_to_keep]

    circle_im = np.zeros((aligned_dim, aligned_dim))

    center = aligned_dim / 2

    peak_rows = []
    for field_peak_i, loc in enumerate(locs):
        if sigproc_params.radial_filter is not None:
            radius = math.sqrt((loc[0] - center) ** 2 + (loc[1] - center) ** 2)
            radius /= center
            if radius >= sigproc_params.radial_filter:
                continue

        imops.set_with_mask_in_place(circle_im, brim_mask, 1, loc=loc, center=True)

        peak_rows += [
            Munch(
                peak_i=0,
                field_peak_i=field_peak_i,
                aln_y=int(loc[0]),
                aln_x=int(loc[1]),
            )
        ]

    peak_df = pd.DataFrame(peak_rows)

    return peak_df, circle_im, aligned_mask_rects
Example #23
0
 def it_can_can_slice_and_dice_with_roi():
     dim = WH(10, 10)
     image = np.zeros(dim)
     roi = ROI(XY(1, 1), dim - WH(2, 2))
     cropped_image = image[roi]
     assert np.array_equal(image[1:9, 1:9], cropped_image[:, :])
Example #24
0
 def it_can_can_slice_an_roi_with_centering():
     roi = ROI(XY(1, 2), WH(2, 4), center=True)
     assert roi[0] == slice(0, 4) and roi[1] == slice(0, 2)
Example #25
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,
    )