File size: 1,826 Bytes
fb6be15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"""Pixel-level edge-density tissue mask for HANCOCK TMA cores (finalized method).
Sobel gradient magnitude -> Gaussian local density -> threshold -> close -> keep
components>=1% -> fill solid -> erode (tight boundary that hugs tissue).
Validated on regular / irregular-fragmented / faint cores; removal 0-13% (outside-core debris).
"""
import numpy as np, cv2


def tissue_mask_px(rgb, sigma=10, thr=2, close=31, erode=25, min_frac=0.01):
    """rgb uint8 HxWx3 -> uint8 {0,1} tissue mask (whole core, debris/background excluded)."""
    gray = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY).astype(np.float32)
    mag = cv2.magnitude(cv2.Sobel(gray, cv2.CV_32F, 1, 0, ksize=3),
                        cv2.Sobel(gray, cv2.CV_32F, 0, 1, ksize=3))
    m = (cv2.GaussianBlur(mag, (0, 0), sigma) > thr).astype(np.uint8)
    m = cv2.morphologyEx(m, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (close, close)))
    nlab, lab, stats, _ = cv2.connectedComponentsWithStats(m, 8)
    keep = np.zeros_like(m); minpx = min_frac * m.size
    for i in range(1, nlab):
        if stats[i, cv2.CC_STAT_AREA] >= minpx: keep[lab == i] = 1
    cnts, _ = cv2.findContours(keep, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    filled = np.zeros_like(keep)
    for c in cnts:
        if cv2.contourArea(c) >= minpx: cv2.drawContours(filled, [c], -1, 1, -1)
    if erode > 1:
        filled = cv2.erode(filled, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (erode, erode)))
    return filled


def cells_in_mask(mask, center_xy):
    """center_xy (N,2) int -> bool (N,) whether each cell center is inside the tissue mask."""
    H, W = mask.shape
    if len(center_xy) == 0: return np.zeros(0, bool)
    xs = np.clip(center_xy[:, 0].astype(int), 0, W - 1)
    ys = np.clip(center_xy[:, 1].astype(int), 0, H - 1)
    return mask[ys, xs] > 0