ibrahim313's picture
Update app.py
4d94df5 verified
raw
history blame
7.66 kB
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()