Source code for spinningdiskanalyzer.imaging.cells

"""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