def circle_locs(im, locs, inner_radius=3, outer_radius=4, fill_mode="nan"): """ Returns a copy of im with circles placed around the locs. Arguments im: The background image locs: Nx2 matrix of peak locations circle_radius: Radius of circle to draw fill_mode: "nan": Use im and overlay with circles of NaNs "index": zero for all background and the loc index otherwise (This causes the loss of the 0-th peak) style_mode: "donut" Draw a 1 pixel donut "solid": Draw a filled circle This can then be visualized like: circle_im = circle_locs(im, locs, fill_mode="nan") z.im(circle_im, _nan_color="red") """ mea = (outer_radius + 1) * 2 + 1 hat = imops.generate_circle_mask(inner_radius, mea) brim = imops.generate_circle_mask(outer_radius, mea) brim = brim & ~hat if fill_mode == "nan": circle_im = np.zeros_like(im) for loc in locs: imops.set_with_mask_in_place(circle_im, brim, 1, loc=loc, center=True) return np.where(circle_im == 1, np.nan, im) if fill_mode == "index": circle_im = np.zeros_like(im) for loc_i, loc in enumerate(locs): imops.set_with_mask_in_place(circle_im, brim, loc_i, loc=loc, center=True) return circle_im
def circle_locs( im, locs, inner_radius=3, outer_radius=4, fill_mode="nan", vals=None, keep_mask=None, peak_iz=None, ): """ Returns a copy of im with circles placed around the locs. Arguments im: The background image locs: Nx2 matrix of peak locations circle_radius: Radius of circle to draw fill_mode: "nan": Use im and overlay with circles of NaNs "index": zero for all background and the loc index otherwise (This causes the loss of the 0-th peak) "one": zero on background one on foreground "vals": zero on background val[loc_i[ for foreground "peak_iz": peak_iz style_mode: "donut" Draw a 1 pixel donut "solid": Draw a filled circle Notes: This can then be visualized like: circle_im = circle_locs(im, locs, fill_mode="nan") z.im(circle_im, _nan_color="red") """ n_locs = len(locs) if keep_mask is None: keep_mask = np.ones((n_locs, ), dtype=bool) mea = (outer_radius + 1) * 2 + 1 hat = imops.generate_circle_mask(inner_radius, mea) brim = imops.generate_circle_mask(outer_radius, mea) brim = brim & ~hat if fill_mode == "nan": circle_im = np.zeros_like(im) for loc_i, (loc, keep) in enumerate(zip(locs, keep_mask)): if keep: imops.set_with_mask_in_place(circle_im, brim, 1, loc=loc, center=True) return np.where(circle_im == 1, np.nan, im) if fill_mode == "index": circle_im = np.zeros_like(im) for loc_i, (loc, keep) in enumerate(zip(locs, keep_mask)): if keep: imops.set_with_mask_in_place(circle_im, brim, loc_i, loc=loc, center=True) return circle_im if fill_mode == "peak_iz": circle_im = np.zeros_like(im) for peak_i, loc, keep in zip(peak_iz, locs, keep_mask): if keep: imops.set_with_mask_in_place(circle_im, brim, peak_i, loc=loc, center=True) return circle_im if fill_mode == "one": circle_im = np.zeros_like(im) for loc_i, (loc, keep) in enumerate(zip(locs, keep_mask)): if keep: imops.set_with_mask_in_place(circle_im, brim, 1, loc=loc, center=True) return circle_im if fill_mode == "vals": check.array_t(vals, shape=(locs.shape[0], )) circle_im = np.zeros_like(im) for loc_i, (loc, val, keep) in enumerate(zip(locs, vals, keep_mask)): if keep: imops.set_with_mask_in_place(circle_im, brim, val, loc=loc, center=True) return circle_im
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
def test_is_sets_with_a_mask(): mask = np.array([[True, False], [False, True]], dtype=bool) imops.set_with_mask_in_place(im2, mask, 0, loc=XY(1, 1)) expected = np.array([[1, 2, 3], [4, 0, 6], [7, 8, 0]]) assert np.array_equal(im2, expected)