from typing import List, Tuple, Dict, Any
from itertools import count
import numpy as np
from scipy.ndimage import percentile_filter
from ..detection.sparsedetect import extendROI
from .. import default_ops
[docs]def create_masks(stats: List[Dict[str, Any]], Ly, Lx, ops=default_ops()):
""" create cell and neuropil masks """
cell_pix = create_cell_pix(stats, Ly=Ly, Lx=Lx,
lam_percentile=ops.get("lam_percentile", 50.0))
cell_masks = [
create_cell_mask(stat, Ly=Ly, Lx=Lx, allow_overlap=ops["allow_overlap"])
for stat in stats
]
if ops.get("neuropil_extract", True):
neuropil_masks = create_neuropil_masks(
ypixs=[stat["ypix"] for stat in stats],
xpixs=[stat["xpix"] for stat in stats], cell_pix=cell_pix,
inner_neuropil_radius=ops["inner_neuropil_radius"],
min_neuropil_pixels=ops["min_neuropil_pixels"],
circular=ops.get("circular_neuropil", False))
else:
neuropil_masks = None
return cell_masks, neuropil_masks
[docs]def create_cell_pix(stats: List[Dict[str, Any]], Ly: int, Lx: int,
lam_percentile: float = 50.0) -> np.ndarray:
"""Returns Ly x Lx array of whether pixel contains a cell (1) or not (0).
lam_percentile allows some pixels with low cell weights to be used,
disable with lam_percentile=0.0
"""
cell_pix = np.zeros((Ly, Lx))
lammap = np.zeros((Ly, Lx))
radii = np.zeros(len(stats))
for ni, stat in enumerate(stats):
radii[ni] = stat["radius"]
ypix = stat["ypix"]
xpix = stat["xpix"]
lam = stat["lam"]
lammap[ypix, xpix] = np.maximum(lammap[ypix, xpix], lam)
radius = np.median(radii)
if lam_percentile > 0.0:
filt = percentile_filter(lammap, percentile=lam_percentile,
size=int(radius * 5))
cell_pix = ~np.logical_or(lammap < filt, lammap == 0)
else:
cell_pix = lammap > 0.0
return cell_pix
[docs]def create_cell_mask(stat: Dict[str, Any], Ly: int, Lx: int,
allow_overlap: bool = False) -> Tuple[np.ndarray, np.ndarray]:
"""
creates cell masks for ROIs in stat and computes radii
Parameters
----------
stat : dictionary "ypix", "xpix", "lam"
Ly : y size of frame
Lx : x size of frame
allow_overlap : whether or not to include overlapping pixels in cell masks
Returns
-------
cell_masks : len ncells, each has tuple of pixels belonging to each cell and weights
lam_normed
"""
mask = ... if allow_overlap else ~stat["overlap"]
cell_mask = np.ravel_multi_index((stat["ypix"], stat["xpix"]), (Ly, Lx))
cell_mask = cell_mask[mask]
lam = stat["lam"][mask]
lam_normed = lam / lam.sum() if lam.size > 0 else np.empty(0)
return cell_mask, lam_normed
[docs]def create_neuropil_masks(ypixs, xpixs, cell_pix, inner_neuropil_radius,
min_neuropil_pixels, circular=False):
""" creates surround neuropil masks for ROIs in stat by EXTENDING ROI (slower if circular)
Parameters
----------
cellpix : 2D array
1 if ROI exists in pixel, 0 if not;
pixels ignored for neuropil computation
Returns
-------
neuropil_masks : list
each element is array of pixels in mask in (Ly*Lx) coordinates
"""
valid_pixels = lambda cell_pix, ypix, xpix: cell_pix[ypix, xpix] < .5
extend_by = 5
Ly, Lx = cell_pix.shape
assert len(xpixs) == len(ypixs)
neuropil_ipix = []
idx = 0
for ypix, xpix in zip(ypixs, xpixs):
neuropil_mask = np.zeros((Ly, Lx), bool)
# extend to get ring of dis-allowed pixels
ypix, xpix = extendROI(ypix, xpix, Ly, Lx, niter=inner_neuropil_radius)
nring = np.sum(valid_pixels(cell_pix, ypix,
xpix)) # count how many pixels are valid
nreps = count()
ypix1, xpix1 = ypix.copy(), xpix.copy()
while next(nreps) < 100 and np.sum(valid_pixels(
cell_pix, ypix1, xpix1)) - nring <= min_neuropil_pixels:
if circular:
ypix1, xpix1 = extendROI(ypix1, xpix1, Ly, Lx,
extend_by) # keep extending
else:
ypix1, xpix1 = np.meshgrid(
np.arange(max(0,
ypix1.min() - extend_by),
min(Ly,
ypix1.max() + extend_by + 1), 1, int),
np.arange(max(0,
xpix1.min() - extend_by),
min(Lx,
xpix1.max() + extend_by + 1), 1, int), indexing="ij")
ix = valid_pixels(cell_pix, ypix1, xpix1)
neuropil_mask[ypix1[ix], xpix1[ix]] = True
neuropil_mask[ypix, xpix] = False
neuropil_ipix.append(np.ravel_multi_index(np.nonzero(neuropil_mask), (Ly, Lx)))
idx += 1
return neuropil_ipix