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