def run_lap(img0, img1, labels0, labels1, DISPLACEMENT=30, MASSTHRES=0.2): '''Linear assignment problem for mammalian cells. Cost matrix is simply the distance. costDie and costBorn are variables changing over frame. Update it through holder. Args: DISPLACEMENT (int): The maximum distance (in pixel) MASSTHRES (float): The maximum difference of total intensity changes. 0.2 means it allows for 20% total intensity changes. ''' labels = -labels1.copy() rps0 = regionprops(labels0, img0) rps1 = regionprops(labels1, img1) if not rps0 or not rps1: return labels0, labels dist = cdist([i.centroid for i in rps0], [i.centroid for i in rps1]) massdiff = calc_massdiff(rps0, rps1) '''search radius is now simply set by maximum displacement possible. In the future, I might add data-driven approach mentioned in LAP paper (supple pg.7)''' dist[dist > DISPLACEMENT] = np.Inf # assign a large cost for unlikely a pair # dist[abs(massdiff) > MASSTHRES] = np.Inf cost = dist if cost.shape[0] == 0 or cost.shape[1] == 0: return labels0, labels # Define initial costBorn and costDie in the first frame if not hasattr(holder, 'cost_born') or not hasattr(holder, 'cost_die'): holder.cost_born = np.percentile(cost[~np.isinf(cost)], 80) holder.cost_die = np.percentile(cost[~np.isinf(cost)], 80) # try: binary_cost = call_lap(cost, holder.cost_die, holder.cost_born) # The first assignment of np.Inf is to reduce calculation of linear assignment. # This part will make sure that cells outside of these range do not get connected. binary_cost[(np.abs(massdiff) > MASSTHRES)] = False binary_cost[(dist > DISPLACEMENT)] = False gp, gc = np.where(binary_cost) idx0, idx1 = list(gp), list(gc) for i0, i1 in zip(idx0, idx1): labels[labels1 == rps1[i1].label] = rps0[i0].label labels0[labels0 == rps0[i0].label] = -rps0[i0].label # update cost linked_dist = [dist[i0, i1] for i0, i1 in zip(idx0, idx1)] if linked_dist: cost = np.max(linked_dist) * 1.05 if cost != 0: # solver freezes if cost is 0 holder.cost_born, holder.cost_die = cost, cost return labels0, labels
def caller(inputs_list, inputs_labels_list, output, primary, secondary): make_dirs(dirname(abspath(output))) inputs_list = [inputs_list, ] if isinstance(inputs_list[0], str) else inputs_list inputs_labels_list = [inputs_labels_list, ] if isinstance(inputs_labels_list[0], str) else inputs_labels_list obj_names = [basename(dirname(i[0])) for i in inputs_labels_list] if primary is None else primary ch_names = [basename(dirname(i[0])) for i in inputs_list] if secondary is None else secondary store = [] for inputs, ch in zip(inputs_list, ch_names): for inputs_labels, obj in zip(inputs_labels_list, obj_names): logger.info("Channel {0}: {1} applied...".format(ch, obj)) for frame, (path, pathl) in enumerate(zip(inputs, inputs_labels)): img, labels = imread(path), lbread(pathl, nonneg=False) cells = regionprops(labels, img) if (labels < 0).any(): cells = add_parent(cells, labels) [setattr(cell, 'frame', frame) for cell in cells] cells = [Cell(cell) for cell in cells] store.append(cells) logger.info("\tmaking dataframe...") df = multi_index([i for ii in store for i in ii], obj, ch) if exists(join(output, 'df.csv')): ex_df = pd.read_csv(join(output, 'df.csv'), index_col=['object', 'ch', 'prop', 'frame']) ex_df.columns = pd.to_numeric(ex_df.columns) ex_df = ex_df.astype(np.float32) df = pd.concat([df, ex_df]) df.to_csv(join(output, 'df.csv')) larr = df2larr(df) larr.save(join(output, 'df.npz')) logger.info("\tdf.npz saved.")
def caller(inputs, inputs_labels, output, functions, params): make_dirs(output) # Make cells. cells are a list of regionproperties or subclasses. logger.info('Postprocess.\tcollecting cells...') store = [] for frame, (path, pathl) in enumerate(zip(inputs, inputs_labels)): img, labels = imread(path), lbread(pathl) cells = regionprops(labels, img) cells = [Cell(cell) for cell in cells] for cell in cells: cell.frame = frame if frame > 0: all_labels = [i.label for i in store[frame - 1]] if cell.label in all_labels: store[frame - 1][all_labels.index(cell.label)].nxt = cell store.append(cells) cells = [i for j in store for i in j] # Each function receives cells (regionprops) and finally return labels generated by cells.label for function, param in zip(functions, params): logger.info('\trunning {0}'.format(function)) func = getattr(postprocess_operation, function) cells = func(cells, **param) logger.info('\tsaving images...') for frame, (path, pathl) in enumerate(zip(inputs, inputs_labels)): labels = cells2labels(cells, frame, lbread(pathl)) imsave(labels, output, path, dtype=np.int16)
def nn_closer(img0, img1, labels0, labels1, DISPLACEMENT=30, MASSTHRES=0.25): labels = -labels1.copy() rps0 = regionprops(labels0, img0) rps1 = regionprops(labels1, img1) if not rps0 or not rps1: return labels0, labels dist = cdist([i.centroid for i in rps0], [i.centroid for i in rps1]) massdiff = calc_massdiff(rps0, rps1) binary_cost = (dist < DISPLACEMENT) * (abs(massdiff) < MASSTHRES) binary_cost = pick_closer_cost(binary_cost, dist) binary_cost = pick_closer_cost(binary_cost.T, dist.T).T idx1, idx0 = find_one_to_one_assign(binary_cost) for i0, i1 in zip(idx0, idx1): labels[labels1 == rps1[i1].label] = rps0[i0].label labels0[labels0 == rps0[i0].label] = -rps0[i0].label return labels0, labels
def caller(inputs_list, inputs_labels_list, output, primary, secondary): make_dirs(dirname(abspath(output))) inputs_list = [ inputs_list, ] if isinstance(inputs_list[0], str) else inputs_list inputs_labels_list = [ inputs_labels_list, ] if isinstance(inputs_labels_list[0], str) else inputs_labels_list obj_names = [basename(dirname(i[0])) for i in inputs_labels_list] if primary is None else primary ch_names = [basename(dirname(i[0])) for i in inputs_list] if secondary is None else secondary for inputs, ch in zip(inputs_list, ch_names): for inputs_labels, obj in zip(inputs_labels_list, obj_names): logger.info("Channel {0}: {1} applied...".format(ch, obj)) arr = np.ones((MAX_NUMCELL, len(PROP_SAVE), len(inputs)), np.float32) * np.nan for frame, (path, pathl) in enumerate(zip(inputs, inputs_labels)): img, labels = imread(path), lbread(pathl, nonneg=False) cells = regionprops(labels, img) if (labels < 0).any(): cells = add_parent(cells, labels) [setattr(cell, 'frame', frame) for cell in cells] cells = [Cell(cell) for cell in cells] tarr = _cells2array(cells) index = tarr[:, 1].astype(np.int32) arr[index, :, frame] = tarr logger.info("\tmaking dataframe...") cellids = np.where(~np.isnan(arr[:, 0, :]).all(axis=1))[0] marr = np.zeros((len(cellids), arr.shape[1], arr.shape[2])) for pn, i in enumerate(cellids): marr[pn] = arr[i] sarr = np.swapaxes(marr, 0, 2) narr = sarr.reshape((sarr.shape[0] * sarr.shape[1], sarr.shape[2]), order='F') index = pd.MultiIndex.from_product( [obj, ch, PROP_SAVE, range(arr.shape[-1])], names=['object', 'ch', 'prop', 'frame']) df = pd.DataFrame(narr, index=index, columns=cellids) if exists(join(output, FILE_NAME + '.csv')): ex_df = pd.read_csv( join(output, FILE_NAME + '.csv'), index_col=['object', 'ch', 'prop', 'frame']) ex_df.columns = pd.to_numeric(ex_df.columns) ex_df = ex_df.astype(np.float32) df = pd.concat([df, ex_df]) df.to_csv(join(output, FILE_NAME + '.csv')) larr = df2larr(df) larr.save(join(output, FILE_NAME + '.npz')) logger.info("\t" + FILE_NAME + ".npz saved.")
def nearest_neighbor(img0, img1, labels0, labels1, DISPLACEMENT=20, MASSTHRES=0.2): """ labels0 and labels1: the positive values for non-tracked objects and the negative values for tracked objects. """ labels = -labels1.copy() rps0 = regionprops(labels0, img0) rps1 = regionprops(labels1, img1) if not rps0 or not rps1: return labels0, labels dist = cdist([i.centroid for i in rps0], [i.centroid for i in rps1]) massdiff = calc_massdiff(rps0, rps1) binary_cost = (dist < DISPLACEMENT) * (abs(massdiff) < MASSTHRES) idx1, idx0 = find_one_to_one_assign(binary_cost) for i0, i1 in zip(idx0, idx1): labels[labels1 == rps1[i1].label] = rps0[i0].label labels0[labels0 == rps0[i0].label] = -rps0[i0].label return labels0, labels
def _wd(labels0, labels, img0, img1): labels1 = -labels.copy() rps0 = regionprops(labels0, img0) from subdetect_operation import watershed_divide # DO NOT MOVE IT from utils.track_utils import _find_match untracked_labels = labels1.copy() untracked_labels[untracked_labels < 0] = 0 wshed_labels = watershed_divide(untracked_labels, regmax=REGMAX, min_size=MIN_SIZE) wshed_labels = label(wshed_labels) store = regionprops(wshed_labels, img1) good_cells = _find_match(rps0, store, DISPLACEMENT, MASSTHRES) for gc in good_cells: # needed to reuse _update_labels_neck_cut gccrds = gc.coords[0] gc.raw_label = labels1[gccrds[0], gccrds[1]] labels0, labels = _update_labels_neck_cut(labels0, labels1, good_cells) labels0, labels = nn_closer(img0, img1, labels0, -labels, DISPLACEMENT, MASSTHRES) return labels0, labels, good_cells
def track_neck_cut(img0, img1, labels0, labels1, DISPLACEMENT=10, MASSTHRES=0.2, EDGELEN=5, THRES_ANGLE=180, WSLIMIT=False, SMALL_RAD=3, CANDS_LIMIT=300): """ Adaptive segmentation by using tracking informaiton. Separate two objects by making a cut at the deflection. For each points on the outline, it will make a triangle separated by EDGELEN and calculates the angle facing inside of concave. The majority of cells need to be tracked before the this method to calculate LARGE_RAD and SMALL_RAD. EDGELEN (int): A length of edges of triangle on the nuclear perimeter. THRES_ANGLE (int): Define the neck points if a triangle has more than this angle. STEPLIM (int): points of neck needs to be separated by at least STEPLIM in parimeters. WSLIMIT (bool): Limit search points to ones overlapped with watershed transformed images. Set it True if calculation is slow. SMALL_RAD (int or None): The smallest radius of candidate objects. If you have many cells, set it to None will infer the radius from previous frame. CANDS_LIMIT(int): use lower if slow. limit a number of searches. """ labels0, labels = nn_closer(img0, img1, labels0, labels1, DISPLACEMENT, MASSTHRES) labels1 = -labels.copy() if SMALL_RAD is None and not hasattr(holder, 'SMALL_RAD'): tracked_area = [i.area for i in regionprops(labels)] holder.SMALL_RAD = np.sqrt(np.percentile(tracked_area, 5) / np.pi) elif SMALL_RAD is not None: holder.SMALL_RAD = SMALL_RAD SMALL_RAD = holder.SMALL_RAD rps0 = regionprops(labels0, img0) unique_labels = np.unique(labels1) if WSLIMIT: wlines = wshed_raw(labels1 > 0, img1) else: wlines = np.ones(labels1.shape, np.bool) store = [] coords_store = [] for label_id in unique_labels: if label_id == 0: continue cc = CellCutter(labels1 == label_id, img1, wlines, small_rad=SMALL_RAD, EDGELEN=EDGELEN, THRES=THRES_ANGLE, CANDS_LIMIT=CANDS_LIMIT) cc.prepare_coords_set() candidates = cc.search_cut_candidates(cc.bw.copy(), cc.coords_set[:CANDS_LIMIT]) for c in candidates: c.raw_label = label_id store.append(candidates) coords_store.append(cc.coords_set) coords_store = [i for i in coords_store if i] # Attempt a first cut. good_cells = _find_best_neck_cut(rps0, store, DISPLACEMENT, MASSTHRES) labels0, labels = _update_labels_neck_cut(labels0, labels1, good_cells) labels0, labels = nn_closer(img0, img1, labels0, -labels, DISPLACEMENT, MASSTHRES) # iteration from here. while good_cells: rps0 = regionprops(labels0, img0) labels1 = -labels.copy() rps0 = regionprops(labels0, img0) unique_labels = np.unique(labels1) store = [] for label_id in unique_labels: if label_id == 0: continue bw = labels1 == label_id coords_set = [ i for i in coords_store if bw[i[0][0][0], i[0][0][1]] ] if not coords_set: continue coords_set = coords_set[0] candidates = cc.search_cut_candidates(bw, coords_set) for c in candidates: c.raw_label = label_id store.append(candidates) coords_store.append(coords_set) good_cells = _find_best_neck_cut(rps0, store, DISPLACEMENT, MASSTHRES) labels0, labels = _update_labels_neck_cut(labels0, labels1, good_cells) labels0, labels = nn_closer(img0, img1, labels0, -labels, DISPLACEMENT, MASSTHRES) return labels0, labels