| 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() |