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
|