|
import cv2 |
|
import numpy as np |
|
import pandas as pd |
|
import gradio as gr |
|
import matplotlib.pyplot as plt |
|
from datetime import datetime |
|
|
|
class BloodCellAnalyzer: |
|
def __init__(self): |
|
|
|
self.min_rbc_area = 400 |
|
self.max_rbc_area = 2000 |
|
self.min_wbc_area = 500 |
|
self.max_wbc_area = 3000 |
|
self.min_circularity = 0.75 |
|
|
|
def detect_cells(self, image): |
|
"""Detect both red and white blood cells using color-based segmentation.""" |
|
if image is None: |
|
return None, [], None |
|
|
|
|
|
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) |
|
|
|
|
|
lower_red1 = np.array([0, 50, 50]) |
|
upper_red1 = np.array([10, 255, 255]) |
|
lower_red2 = np.array([160, 50, 50]) |
|
upper_red2 = np.array([180, 255, 255]) |
|
|
|
red_mask1 = cv2.inRange(hsv, lower_red1, upper_red1) |
|
red_mask2 = cv2.inRange(hsv, lower_red2, upper_red2) |
|
red_mask = cv2.bitwise_or(red_mask1, red_mask2) |
|
|
|
|
|
lower_blue = np.array([90, 50, 50]) |
|
upper_blue = np.array([130, 255, 255]) |
|
blue_mask = cv2.inRange(hsv, lower_blue, upper_blue) |
|
|
|
|
|
kernel = np.ones((3,3), np.uint8) |
|
red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_OPEN, kernel, iterations=1) |
|
red_mask = cv2.morphologyEx(red_mask, cv2.MORPH_CLOSE, kernel, iterations=1) |
|
blue_mask = cv2.morphologyEx(blue_mask, cv2.MORPH_OPEN, kernel, iterations=1) |
|
blue_mask = cv2.morphologyEx(blue_mask, cv2.MORPH_CLOSE, kernel, iterations=1) |
|
|
|
|
|
rbc_contours, _ = cv2.findContours(red_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
|
wbc_contours, _ = cv2.findContours(blue_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
|
|
|
cells = [] |
|
valid_contours = [] |
|
|
|
|
|
for i, contour in enumerate(rbc_contours): |
|
area = cv2.contourArea(contour) |
|
perimeter = cv2.arcLength(contour, True) |
|
circularity = 4 * np.pi * area / (perimeter * perimeter) if perimeter > 0 else 0 |
|
|
|
if (self.min_rbc_area < area < self.max_rbc_area and |
|
circularity > self.min_circularity): |
|
M = cv2.moments(contour) |
|
if M["m00"] != 0: |
|
cx = int(M["m10"] / M["m00"]) |
|
cy = int(M["m01"] / M["m00"]) |
|
cells.append({ |
|
'label': len(valid_contours) + 1, |
|
'type': 'RBC', |
|
'area': area, |
|
'circularity': circularity, |
|
'centroid_x': cx, |
|
'centroid_y': cy |
|
}) |
|
valid_contours.append(contour) |
|
|
|
|
|
for i, contour in enumerate(wbc_contours): |
|
area = cv2.contourArea(contour) |
|
perimeter = cv2.arcLength(contour, True) |
|
circularity = 4 * np.pi * area / (perimeter * perimeter) if perimeter > 0 else 0 |
|
|
|
if (self.min_wbc_area < area < self.max_wbc_area): |
|
M = cv2.moments(contour) |
|
if M["m00"] != 0: |
|
cx = int(M["m10"] / M["m00"]) |
|
cy = int(M["m01"] / M["m00"]) |
|
cells.append({ |
|
'label': len(valid_contours) + 1, |
|
'type': 'WBC', |
|
'area': area, |
|
'circularity': circularity, |
|
'centroid_x': cx, |
|
'centroid_y': cy |
|
}) |
|
valid_contours.append(contour) |
|
|
|
return valid_contours, cells, red_mask |
|
|
|
def analyze_image(self, image): |
|
"""Analyze the blood cell image 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] |
|
color = (0, 0, 255) if cell['type'] == 'RBC' else (255, 0, 0) |
|
cv2.drawContours(vis_img, [contour], -1, color, 2) |
|
cv2.putText(vis_img, f"{cell['type']}", |
|
(cell['centroid_x'], cell['centroid_y']), |
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) |
|
|
|
|
|
df = pd.DataFrame(cells) |
|
|
|
|
|
if not df.empty: |
|
rbc_count = len(df[df['type'] == 'RBC']) |
|
wbc_count = len(df[df['type'] == 'WBC']) |
|
|
|
summary_stats = { |
|
'total_rbc': rbc_count, |
|
'total_wbc': wbc_count, |
|
'rbc_avg_size': df[df['type'] == 'RBC']['area'].mean() if rbc_count > 0 else 0, |
|
'wbc_avg_size': df[df['type'] == 'WBC']['area'].mean() if wbc_count > 0 else 0, |
|
} |
|
|
|
|
|
for k, v in summary_stats.items(): |
|
df[k] = v |
|
|
|
|
|
fig = self.generate_analysis_plots(df) |
|
|
|
return vis_img, mask, fig, df |
|
|
|
def generate_analysis_plots(self, df): |
|
"""Generate analysis plots for the detected cells.""" |
|
if df.empty: |
|
return None |
|
|
|
plt.style.use('dark_background') |
|
fig, axes = plt.subplots(2, 2, figsize=(12, 10)) |
|
|
|
|
|
cell_counts = df['type'].value_counts() |
|
axes[0, 0].bar(cell_counts.index, cell_counts.values, color=['red', 'blue']) |
|
axes[0, 0].set_title('Cell Count by Type') |
|
|
|
|
|
for cell_type, color in zip(['RBC', 'WBC'], ['red', 'blue']): |
|
if len(df[df['type'] == cell_type]) > 0: |
|
axes[0, 1].hist(df[df['type'] == cell_type]['area'], |
|
bins=20, alpha=0.5, color=color, label=cell_type) |
|
axes[0, 1].set_title('Cell Size Distribution') |
|
axes[0, 1].legend() |
|
|
|
|
|
for cell_type, color in zip(['RBC', 'WBC'], ['red', 'blue']): |
|
cell_data = df[df['type'] == cell_type] |
|
if len(cell_data) > 0: |
|
axes[1, 0].scatter(cell_data['area'], cell_data['circularity'], |
|
c=color, label=cell_type, alpha=0.6) |
|
axes[1, 0].set_title('Area vs Circularity') |
|
axes[1, 0].legend() |
|
|
|
|
|
for cell_type, color in zip(['RBC', 'WBC'], ['red', 'blue']): |
|
cell_data = df[df['type'] == cell_type] |
|
if len(cell_data) > 0: |
|
axes[1, 1].scatter(cell_data['centroid_x'], cell_data['centroid_y'], |
|
c=color, label=cell_type, alpha=0.6) |
|
axes[1, 1].set_title('Spatial Distribution') |
|
axes[1, 1].legend() |
|
|
|
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 red and white blood cells." |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch() |