|
import cv2 |
|
import numpy as np |
|
import pandas as pd |
|
import gradio as gr |
|
import matplotlib.pyplot as plt |
|
from datetime import datetime |
|
|
|
def preprocess_image(image): |
|
"""Enhance image contrast, apply thresholding, and clean noise.""" |
|
if len(image.shape) == 2: |
|
image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) |
|
|
|
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) |
|
|
|
|
|
blurred = cv2.GaussianBlur(gray, (5, 5), 0) |
|
|
|
|
|
_, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) |
|
|
|
|
|
kernel = np.ones((3, 3), np.uint8) |
|
clean_mask = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel, iterations=2) |
|
clean_mask = cv2.morphologyEx(clean_mask, cv2.MORPH_OPEN, kernel, iterations=1) |
|
|
|
return clean_mask |
|
|
|
def detect_blood_cells(image): |
|
"""Detect blood cells and extract features.""" |
|
mask = preprocess_image(image) |
|
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
|
|
|
features = [] |
|
total_area = 0 |
|
|
|
for i, contour in enumerate(contours, 1): |
|
area = cv2.contourArea(contour) |
|
perimeter = cv2.arcLength(contour, True) |
|
circularity = (4 * np.pi * area / (perimeter * perimeter)) if perimeter > 0 else 0 |
|
|
|
|
|
if 100 < area < 5000 and circularity > 0.7: |
|
M = cv2.moments(contour) |
|
if M["m00"] != 0: |
|
cx = int(M["m10"] / M["m00"]) |
|
cy = int(M["m01"] / M["m00"]) |
|
features.append({ |
|
'ID': i, 'Area': area, 'Perimeter': perimeter, |
|
'Circularity': circularity, 'Centroid_X': cx, 'Centroid_Y': cy |
|
}) |
|
total_area += area |
|
|
|
|
|
avg_cell_size = total_area / len(features) if features else 0 |
|
cell_density = len(features) / (image.shape[0] * image.shape[1]) |
|
|
|
summary = { |
|
'Total Cells': len(features), |
|
'Avg Cell Size': avg_cell_size, |
|
'Cell Density': cell_density |
|
} |
|
|
|
return contours, features, mask, summary |
|
|
|
def process_image(image): |
|
if image is None: |
|
return None, None, None, None, None |
|
|
|
contours, features, mask, summary = detect_blood_cells(image) |
|
vis_img = image.copy() |
|
|
|
for feature in features: |
|
contour = contours[feature['ID'] - 1] |
|
cv2.drawContours(vis_img, [contour], -1, (0, 255, 0), 2) |
|
cv2.putText(vis_img, str(feature['ID']), (feature['Centroid_X'], feature['Centroid_Y']), |
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) |
|
|
|
df = pd.DataFrame(features) |
|
return vis_img, mask, df, summary |
|
|
|
def analyze(image): |
|
vis_img, mask, df, summary = process_image(image) |
|
|
|
plt.style.use('dark_background') |
|
fig, axes = plt.subplots(1, 2, figsize=(12, 5)) |
|
|
|
if not df.empty: |
|
axes[0].hist(df['Area'], bins=20, color='cyan', edgecolor='black') |
|
axes[0].set_title('Cell Size Distribution') |
|
|
|
axes[1].scatter(df['Area'], df['Circularity'], alpha=0.6, c='magenta') |
|
axes[1].set_title('Area vs Circularity') |
|
|
|
return vis_img, mask, fig, df, summary |
|
|
|
|
|
demo = gr.Interface( |
|
fn=analyze, |
|
inputs=gr.Image(type="numpy"), |
|
outputs=[ |
|
gr.Image(label="Processed Image"), |
|
gr.Image(label="Binary Mask"), |
|
gr.Plot(label="Analysis Plots"), |
|
gr.Dataframe(label="Detected Cells Data"), |
|
gr.JSON(label="Summary Statistics") |
|
] |
|
) |
|
|
|
demo.launch() |
|
|