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) # Convert to multiple color spaces for robust detection hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV) lab = cv2.cvtColor(image, cv2.COLOR_RGB2LAB) gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) # Create masks for different color ranges 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])) # Combine masks combined_mask = cv2.bitwise_or(hsv_mask, lab_mask) # Apply advanced morphological operations 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) # Apply distance transform to separate touching cells 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 # Calculate additional shape features hull = cv2.convexHull(contour) hull_area = cv2.contourArea(hull) solidity = float(area) / hull_area if hull_area > 0 else 0 # Calculate moments and orientation 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 # Calculate eccentricity using ellipse fitting 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) # Find contours contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # Extract features and filter cells cells = [] valid_contours = [] for i, contour in enumerate(contours): features = self.extract_cell_features(contour) # Apply multiple criteria for cell validation 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 # Detect cells and extract features contours, cells, mask = self.detect_cells(image) vis_img = image.copy() # Draw detections and labels 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) # Create DataFrame and calculate summary statistics 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()}) # Generate visualizations 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)) # Create subplot grid 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, :]) # Cell size distribution ax1.hist(df['area'], bins=20, color='cyan', edgecolor='black') ax1.set_title('Cell Size Distribution') ax1.set_xlabel('Area') ax1.set_ylabel('Count') # Area vs Circularity 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') # Eccentricity distribution ax3.hist(df['eccentricity'], bins=15, color='magenta', edgecolor='black') ax3.set_title('Eccentricity Distribution') ax3.set_xlabel('Eccentricity') ax3.set_ylabel('Count') # Cell position scatter plot 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 # Create Gradio interface 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()