|
import cv2 |
|
import numpy as np |
|
import pandas as pd |
|
import gradio as gr |
|
import matplotlib.pyplot as plt |
|
from datetime import datetime |
|
from sklearn.cluster import DBSCAN |
|
from scipy import ndimage |
|
|
|
class BloodCellAnalyzer: |
|
def __init__(self): |
|
self.min_cell_area = 100 |
|
self.max_cell_area = 5000 |
|
self.min_circularity = 0.7 |
|
|
|
def preprocess_image(self, image): |
|
"""Enhanced image preprocessing with multiple color spaces and filtering.""" |
|
if len(image.shape) == 2: |
|
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) |
|
|
|
|
|
hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV) |
|
lab = cv2.cvtColor(image, cv2.COLOR_RGB2LAB) |
|
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) |
|
|
|
|
|
hsv_mask = cv2.inRange(hsv, np.array([0, 20, 20]), np.array([180, 255, 255])) |
|
lab_mask = cv2.inRange(lab, np.array([20, 120, 120]), np.array([200, 140, 140])) |
|
|
|
|
|
combined_mask = cv2.bitwise_or(hsv_mask, lab_mask) |
|
|
|
|
|
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) |
|
clean_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel, iterations=2) |
|
clean_mask = cv2.morphologyEx(clean_mask, cv2.MORPH_OPEN, kernel, iterations=1) |
|
|
|
|
|
dist_transform = cv2.distanceTransform(clean_mask, cv2.DIST_L2, 5) |
|
_, sure_fg = cv2.threshold(dist_transform, 0.5 * dist_transform.max(), 255, 0) |
|
|
|
return clean_mask.astype(np.uint8), sure_fg.astype(np.uint8) |
|
|
|
def extract_cell_features(self, contour): |
|
"""Extract comprehensive features for each detected cell.""" |
|
area = cv2.contourArea(contour) |
|
perimeter = cv2.arcLength(contour, True) |
|
circularity = 4 * np.pi * area / (perimeter ** 2) if perimeter > 0 else 0 |
|
|
|
|
|
hull = cv2.convexHull(contour) |
|
hull_area = cv2.contourArea(hull) |
|
solidity = float(area) / hull_area if hull_area > 0 else 0 |
|
|
|
|
|
moments = cv2.moments(contour) |
|
cx = int(moments['m10'] / moments['m00']) if moments['m00'] != 0 else 0 |
|
cy = int(moments['m01'] / moments['m00']) if moments['m00'] != 0 else 0 |
|
|
|
|
|
if len(contour) >= 5: |
|
(x, y), (MA, ma), angle = cv2.fitEllipse(contour) |
|
eccentricity = np.sqrt(1 - (ma / MA) ** 2) if MA > 0 else 0 |
|
else: |
|
eccentricity = 0 |
|
angle = 0 |
|
|
|
return { |
|
'area': area, |
|
'perimeter': perimeter, |
|
'circularity': circularity, |
|
'solidity': solidity, |
|
'eccentricity': eccentricity, |
|
'orientation': angle, |
|
'centroid_x': cx, |
|
'centroid_y': cy |
|
} |
|
|
|
def detect_cells(self, image): |
|
"""Detect and analyze blood cells with advanced filtering.""" |
|
mask, sure_fg = self.preprocess_image(image) |
|
|
|
|
|
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
|
|
|
|
|
cells = [] |
|
valid_contours = [] |
|
|
|
for i, contour in enumerate(contours): |
|
features = self.extract_cell_features(contour) |
|
|
|
|
|
if (self.min_cell_area < features['area'] < self.max_cell_area and |
|
features['circularity'] > self.min_circularity and |
|
features['solidity'] > 0.8): |
|
|
|
features['label'] = i + 1 |
|
cells.append(features) |
|
valid_contours.append(contour) |
|
|
|
return valid_contours, cells, mask |
|
|
|
def analyze_image(self, image): |
|
"""Perform comprehensive image analysis and generate visualizations.""" |
|
if image is None: |
|
return None, None, None, None |
|
|
|
|
|
contours, cells, mask = self.detect_cells(image) |
|
vis_img = image.copy() |
|
|
|
|
|
for cell in cells: |
|
contour = contours[cell['label'] - 1] |
|
cv2.drawContours(vis_img, [contour], -1, (0, 255, 0), 2) |
|
cv2.putText(vis_img, str(cell['label']), |
|
(cell['centroid_x'], cell['centroid_y']), |
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) |
|
|
|
|
|
df = pd.DataFrame(cells) |
|
if not df.empty: |
|
summary_stats = { |
|
'total_cells': len(cells), |
|
'avg_cell_size': df['area'].mean(), |
|
'std_cell_size': df['area'].std(), |
|
'avg_circularity': df['circularity'].mean(), |
|
'cell_density': len(cells) / (image.shape[0] * image.shape[1]) |
|
} |
|
df = df.assign(**{k: [v] * len(df) for k, v in summary_stats.items()}) |
|
|
|
|
|
fig = self.generate_analysis_plots(df) |
|
|
|
return vis_img, mask, fig, df |
|
|
|
def generate_analysis_plots(self, df): |
|
"""Generate comprehensive analysis plots.""" |
|
if df.empty: |
|
return None |
|
|
|
plt.style.use('dark_background') |
|
fig = plt.figure(figsize=(15, 10)) |
|
|
|
|
|
gs = plt.GridSpec(2, 3, figure=fig) |
|
ax1 = fig.add_subplot(gs[0, 0]) |
|
ax2 = fig.add_subplot(gs[0, 1]) |
|
ax3 = fig.add_subplot(gs[0, 2]) |
|
ax4 = fig.add_subplot(gs[1, :]) |
|
|
|
|
|
ax1.hist(df['area'], bins=20, color='cyan', edgecolor='black') |
|
ax1.set_title('Cell Size Distribution') |
|
ax1.set_xlabel('Area') |
|
ax1.set_ylabel('Count') |
|
|
|
|
|
scatter = ax2.scatter(df['area'], df['circularity'], |
|
c=df['solidity'], cmap='viridis', alpha=0.6) |
|
ax2.set_title('Area vs Circularity') |
|
ax2.set_xlabel('Area') |
|
ax2.set_ylabel('Circularity') |
|
plt.colorbar(scatter, ax=ax2, label='Solidity') |
|
|
|
|
|
ax3.hist(df['eccentricity'], bins=15, color='magenta', edgecolor='black') |
|
ax3.set_title('Eccentricity Distribution') |
|
ax3.set_xlabel('Eccentricity') |
|
ax3.set_ylabel('Count') |
|
|
|
|
|
scatter = ax4.scatter(df['centroid_x'], df['centroid_y'], |
|
c=df['area'], cmap='plasma', alpha=0.6, s=100) |
|
ax4.set_title('Cell Positions and Sizes') |
|
ax4.set_xlabel('X Position') |
|
ax4.set_ylabel('Y Position') |
|
plt.colorbar(scatter, ax=ax4, label='Cell Area') |
|
|
|
plt.tight_layout() |
|
return fig |
|
|
|
|
|
analyzer = BloodCellAnalyzer() |
|
demo = gr.Interface( |
|
fn=analyzer.analyze_image, |
|
inputs=gr.Image(type="numpy"), |
|
outputs=[ |
|
gr.Image(label="Detected Cells"), |
|
gr.Image(label="Segmentation Mask"), |
|
gr.Plot(label="Analysis Plots"), |
|
gr.DataFrame(label="Cell Data") |
|
], |
|
title="Blood Cell Analysis Tool", |
|
description="Upload an image to analyze blood cells and extract features." |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch() |