def prepare_stack( stack: Array, channel_labels: Series, compartment: str = "nuclear", channel_exclude: Series = None, ) -> Array: assert compartment in ["nuclear", "cytoplasm", "both"] if channel_exclude is not None: stack = stack[~channel_exclude] channel_labels = channel_labels[~channel_exclude.values] # Get nuclear channels nuclear_chs = channel_labels.str.contains("DNA") nucl = np.asarray([eq(x) for x in stack[nuclear_chs]]).mean(0) if compartment == "nuclear": return nucl # Get cytoplasmatic channels cyto_chs = ~channel_labels.str.contains("DNA|Ki67|SMA") cyto = np.asarray([eq(x) for x in stack[~cyto_chs]]).mean(0) if compartment == "cytoplasm": return cyto # Combined together and expand to 4D return np.stack((nucl, cyto))
def stardist_segment_nuclei(image: Array, model_str: str = "2D_versatile_fluo") -> Array: from stardist.models import StarDist2D model = StarDist2D.from_pretrained(model_str) mask, _ = model.predict_instances(eq(image)) return mask
def cell_type_adjancency( self, rois: Optional[List["ROI"]] = None, output_prefix: Optional[Path] = None, ) -> None: rois = rois or self.rois output_prefix = output_prefix or self.root_dir / "single_cell" / ( self.name + ".cluster_adjacency_graph.") # TODO: check default input # Plot adjancency for all ROIs next to each other and across adj_matrices = pd.concat([ pd.read_csv( f"{output_prefix}{roi.name}.norm_over_random.csv", index_col=0, ).assign(roi=roi.roi_number) for roi in rois ]) # g2 = nx.readwrite.read_gpickle(roi_prefix + "neighbor_graph.gpickle") mean_ = (adj_matrices.drop( "roi", axis=1).groupby(level=0).mean().sort_index(0).sort_index(1)) adj_matrices = adj_matrices.append(mean_.assign(roi="mean")) m = self.n_rois + 1 nrows = 3 fig, _ax = plt.subplots(nrows, m, figsize=(4 * m, 4 * nrows), sharex=False, sharey=False) v = np.nanstd(adj_matrices.drop("roi", axis=1).values) kws = dict( cmap="RdBu_r", center=0, square=True, xticklabels=True, yticklabels=True, vmin=-v, vmax=v, ) for i, roi in enumerate(rois): _ax[0, i].set_title(roi.name) _ax[0, i].imshow(eq(roi.stack.mean(0))) _ax[0, i].axis("off") __x = ( adj_matrices.loc[adj_matrices["roi"] == roi.roi_number].drop( "roi", axis=1).reindex(index=mean_.index, columns=mean_.index).fillna(0)) sns.heatmap(__x, ax=_ax[1, i], **kws) sns.heatmap(__x - mean_, ax=_ax[2, i], **kws) _ax[1, 0].set_ylabel("Observed ratios") _ax[2, 0].set_ylabel("Ratio difference to mean") _ax[1, -1].set_title("ROI mean") sns.heatmap(mean_, ax=_ax[1, -1], **kws) _ax[0, -1].axis("off") _ax[2, -1].axis("off") share_axes_by(_ax[1:], "both") fig.savefig(output_prefix + "roi_over_mean_rois.image_clustermap.svg", **FIG_KWS)
def plot_probabilities_and_segmentation( self, axes: Optional[Sequence[Axis]] = None, add_scale: bool = True) -> Optional[Figure]: """ Visualize channel mean, DNA channel, segmentation probabilities and the segmented nuclei and cells. If `axes` is given it must have length 5 """ probabilities = self.probabilities if probabilities.shape != self.cell_mask.shape: probabilities = ndi.zoom(self.probabilities, (1, 0.5, 0.5)) probabilities = np.moveaxis(probabilities, 0, -1) probabilities = probabilities / probabilities.max() dna_label, dna, minmax = self._get_channel("DNA", dont_warn=True) nuclei = self._get_input_filename("nuclei_mask").exists() ncols = 5 if nuclei else 4 if axes is None: fig, _axes = plt.subplots( 1, ncols, figsize=(ncols * 4, 4), gridspec_kw=dict(wspace=0.05), sharex=True, sharey=True, ) else: _axes = axes _axes[0].set_ylabel(self.name) _axes[0].set_title("Channel mean") _axes[0].imshow( self._get_channel("mean", equalize=True, minmax=True)[1]) _axes[1].set_title(dna_label) _axes[1].imshow(eq(dna)) _axes[2].set_title("Probabilities") _axes[2].imshow(probabilities) i = 0 if nuclei: _axes[3 + i].set_title("Nuclei") _axes[3 + i].imshow(self.nuclei_mask > 0, cmap="binary") i += 1 _axes[3 + i].set_title("Cells") _axes[3 + i].imshow(self.cell_mask > 0, cmap="binary") if add_scale: _add_scale(_axes[3 + i]) # To plot jointly # _axes[5].imshow(probabilities) # _axes[5].contour(self.cell_mask, cmap="Blues") # _axes[5].contour(self.nuclei_mask, cmap="Reds") for _ax in _axes: _ax.axis("off") return fig if axes is None else None
def write_image_to_file( arr: Array, channel_labels: Sequence, output_prefix: Path, file_format: str = "png", ) -> None: if len(arr.shape) != 3: skimage.io.imsave( output_prefix + "." + "channel_mean" + "." + file_format, arr) else: __s = np.multiply(eq(arr.mean(axis=0)), 256).astype(np.uint8) skimage.io.imsave( output_prefix + "." + "channel_mean" + "." + file_format, __s) for channel, label in tqdm(enumerate(channel_labels), total=arr.shape[0]): skimage.io.imsave( output_prefix + "." + label + "." + file_format, np.multiply(arr[channel], 256).astype(np.uint8), )
def merge_channels( arr: Array, output_colors: Optional[List[str]] = None, return_colors: bool = False, ) -> Union[Array, Tuple[Array, List[Tuple[float, float, float]]]]: """ Assumes [0, 1] float array. to is a tuple of 3 colors. """ # defaults = list(matplotlib.colors.TABLEAU_COLORS.values()) n_channels = arr.shape[0] if output_colors is None: target_colors = [ matplotlib.colors.to_rgb(col) for col in DEFAULT_CHANNEL_COLORS[:n_channels] ] if (n_channels == 3) and output_colors is None: m = np.moveaxis(np.asarray([eq(x) for x in arr]), 0, -1) res = (m - m.min((0, 1))) / (m.max((0, 1)) - m.min((0, 1))) return res if not return_colors else (res, target_colors) elif isinstance(output_colors, (list, tuple)): assert len(output_colors) == n_channels target_colors = [matplotlib.colors.to_rgb(col) for col in output_colors] # work in int space to avoid float underflow if arr.min() >= 0 and arr.max() <= 1: arr *= 256 else: arr = saturize(arr) * 256 res = np.zeros(arr.shape[1:] + (3,)) for i in range(n_channels): for j in range(3): res[:, :, j] = res[:, :, j] + arr[i] * target_colors[i][j] # return saturize(res) if not return_colors else (saturize(res), target_colors) return res if not return_colors else (res, target_colors)
def read_image_from_file(file: Path, equalize: bool = False) -> Array: """ Read images from a tiff or hdf5 file into a numpy array. Channels, if existing will be in first array dimension. If `equalize` is :obj:`True`, convert to float type bounded at [0, 1]. """ if not file.exists(): raise FileNotFoundError(f"Could not find file: '{file}") # if str(file).endswith("_mask.tiff"): # arr = tifffile.imread(file) > 0 if file.endswith(".ome.tiff"): arr = tifffile.imread(str(file), is_ome=True) elif file.endswith(".tiff"): arr = tifffile.imread(str(file)) elif file.endswith(".h5"): with h5py.File(file, "r") as __f: arr = np.asarray(__f[list(__f.keys())[0]]) if len(arr.shape) == 3: if min(arr.shape) == arr.shape[-1]: arr = np.moveaxis(arr, -1, 0) if equalize: arr = eq(arr) return arr
def get_mean_all_channels(self) -> Array: """Get an array with mean of all channels""" return eq(self.stack.mean(axis=0))
def stack_eq(self): """Same as `stack` but equalized per channel.""" if self._stack_eq is not None: return self._stack_eq return np.asarray([eq(x) for x in self.stack])