"""Segmentation of cells in thresholded image"""
from . import filters
import cv2
from numpy import ndarray
from pandas import DataFrame
from pandas import read_csv
from math import sqrt
from pathlib import Path
from logging import getLogger
log = getLogger(__name__)
[docs]
def find(
image: ndarray,
x: int, y: int, r: int,
r_reltol: float = 0.004,
area_min: int = 6,
area_max: int = 1000,
) -> DataFrame:
"""
Performs the cell segmentation by finding connected components.
The center `(x, y)` and radius `r` of the circle must be given.
`r_reltol` denotes the relative tolerance for considering cells as
outside the circle, even though strictly speaking they are still
within the radius. Detected cells must have an area in pixels
between `area_min` and `area_max`.
"""
log.info('Segmenting cells in image.')
image = filters.white_on_black(image)
(num_labels, _, stats, centroids) = cv2.connectedComponentsWithStats(
image, labels=4, stats=cv2.CV_32S)
(height, width) = image.shape
tolerance = r_reltol * width
if r_reltol * height > tolerance:
tolerance = r_reltol * height
r_corrected = r - tolerance
cells = []
count = 1
for i in range(centroids.shape[0]):
x0 = centroids[i, 0]
y0 = centroids[i, 1]
x1 = stats[i, cv2.CC_STAT_LEFT]
y1 = stats[i, cv2.CC_STAT_TOP]
w = stats[i, cv2.CC_STAT_WIDTH]
h = stats[i, cv2.CC_STAT_HEIGHT]
area = stats[i][cv2.CC_STAT_AREA]
d_abs = sqrt( (x0 - x)**2 + (y0 - y)**2 )
d_rel = d_abs / r
if d_abs < r_corrected and area_min <= area <= area_max:
cells.append( (count, x0, y0, x1, y1, w, h, area, d_abs, d_rel) )
count += 1
log.debug(f'Found {count} cells among {num_labels} connected components.')
cells = DataFrame(cells)
cells.columns = (
'i', 'x', 'y',
'left', 'top', 'width', 'height',
'area', 'distance_abs', 'distance_rel',
)
return cells
[docs]
def draw(canvas: ndarray, cells: DataFrame) -> ndarray:
"""
Draws the bounding boxes of given `cells` onto the image `canvas`.
This is meant as an illustration rather than doing anything
computational with it.
"""
log.info('Marking cells on image.')
image = filters.black_on_white(canvas.copy())
black = 0
for cell in cells.itertuples():
x1 = cell.left
x2 = x1 + cell.width
y1 = cell.top
y2 = y1 + cell.height
cv2.line(image, (x1, y1), (x2, y1), color=black, thickness=4)
cv2.line(image, (x2, y1), (x2, y2), color=black, thickness=4)
cv2.line(image, (x2, y2), (x1, y2), color=black, thickness=4)
cv2.line(image, (x1, y2), (x1, y1), color=black, thickness=4)
return image
[docs]
def write(file: Path, cells: DataFrame):
"""
Writes cell-segmentation results to given `file`.
The data is written as a tab-separated table with one header row
containing the column titles of the `cells` DataFrame.
"""
log.info('Writing cell locations to file.')
cells.to_csv(file, sep='\t', index=False)
[docs]
def read(file: Path) -> DataFrame:
"""
Reads cell-segmentation results from given `file`.
The data is expected to be tab-separated table with one header row,
just like the one produced by `write()`.
"""
log.info('Reading cell locations from file.')
cell_data = read_csv(file, sep='\t')
return cell_data