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 )
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()
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)
def render(self, im, cy_i): if self.std_x is None: self.std_x = [self.std] if self.std_y is None: self.std_y = [self.std] n_locs = len(self.locs) if len(self.std_x) != n_locs: self.std_x = np.repeat(self.std_x, (n_locs, )) if len(self.std_y) != n_locs: self.std_y = np.repeat(self.std_y, (n_locs, )) super().render(im, cy_i) mea = 17 for loc, amp, std_x, std_y in zip(self.locs, self.amps, self.std_x, self.std_y): frac_x = np.modf(loc[0])[0] frac_y = np.modf(loc[1])[0] peak_im = imops.gauss2_rho_form( amp=amp, std_x=std_x, std_y=std_y, pos_x=mea // 2 + frac_x, pos_y=mea // 2 + frac_y, rho=0.0, const=0.0, mea=mea, ) imops.accum_inplace(im, peak_im, loc=YX(*np.floor(loc)), center=True)
def render(self, im, cy_i): super().render(im, cy_i) for loc, amp, z_i in zip(self.locs, self.amps, self.z_iz): frac_part, int_part = np.modf(loc) shifted_peak_im = imops.sub_pixel_shift(self.z_to_psf[z_i], frac_part) imops.accum_inplace(im, amp * shifted_peak_im, loc=YX(*int_part), center=True)
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)
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
def render(self, im, fl_i, ch_i, cy_i, aln_offset): super().render(im, fl_i, ch_i, cy_i, aln_offset) ch_scale = 1.0 if self._channel_scale_factor is not None: ch_scale = self._channel_scale_factor[ch_i] for loc, amp, k in zip(self.locs, self._amps, self.row_k): loc = loc + aln_offset if isinstance(amp, np.ndarray): amp = amp[cy_i] psf_im, accum_to_loc = self.reg_psf.render_at_loc( loc, amp=ch_scale * k * amp, const=0.0 ) imops.accum_inplace(im, psf_im, loc=YX(accum_to_loc), center=False)
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)
def _step_2c_composite_channels(chcy_ims, medians_by_ch_cy, sigproc_params): """ Merges specified channels for each cycle after background subtraction. The channel list is sometimes partial because there are some experiments where data was recorded in a channel that is bad. Returns: composite cy_ims (channels merged) after background subtraction """ n_outchannels, n_inchannels, n_cycles, dim = sigproc_params.channels_cycles_dim dst_cy_ims = np.zeros((n_cycles, dim, dim)) for cy in range(n_cycles): for inch in range(n_inchannels): if inch in sigproc_params.channel_indices_for_alignment: imops.accum_inplace( dst_cy_ims[cy], (chcy_ims[inch, cy] - medians_by_ch_cy[inch, cy]).clip(min=0), ) return dst_cy_ims
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)
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, )
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()
def render(self, im, cy_i): super().render(im, cy_i) bg = np.random.normal(loc=self.bias, scale=self.std, size=self.dim) imops.accum_inplace(im, bg, XY(0, 0), center=False)
def it_adds_a_sub_image_into_a_target_with_clipping(): dst = np.ones(WH(2, 2)) src = np.ones(WH(4, 4)) imops.accum_inplace(dst, src, XY(-1, -1)) good = np.array([[2, 2], [2, 2]]) assert (dst == good).all()
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
def it_handles_smudged(): true_aln_offsets, chcy_ims = _synth() smudge_im = np.full((400, 400), 1000) imops.accum_inplace(chcy_ims[0, 0], smudge_im, loc=XY(100, 100)) pred_cy_offsets = align_ims(chcy_ims[0]) assert np.all(np.abs(true_aln_offsets - pred_cy_offsets) < 0.16)
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, )