patcdaniel commited on
Commit
16d777e
·
verified ·
1 Parent(s): 05dd2f5

Initial upload: Gradio app for Pseudo-nitzschia cell counting

Browse files
Files changed (3) hide show
  1. README.md +15 -6
  2. app.py +147 -0
  3. requirements.txt +5 -0
README.md CHANGED
@@ -1,12 +1,21 @@
1
  ---
2
- title: Pseudo Nitzschia Counter
3
- emoji: 😻
4
- colorFrom: indigo
5
- colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.9.0
8
  app_file: app.py
9
  pinned: false
 
 
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
1
  ---
2
+ title: Pseudo-nitzschia Cell Counter
3
+ emoji: 🔬
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: gradio
7
+ sdk_version: "5.23.0"
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
+ models:
12
+ - patcdaniel/pseudo-nitzschia-yolo11s-seg
13
+ datasets:
14
+ - patcdaniel/pseudo-nitzschia-cell-segmentation
15
  ---
16
 
17
+ # Pseudo-nitzschia Cell Counter
18
+
19
+ Drag and drop an IFCB image of a *Pseudo-nitzschia* chain colony to automatically count individual cells using instance segmentation.
20
+
21
+ **Model:** [YOLO11s-seg](https://huggingface.co/patcdaniel/pseudo-nitzschia-yolo11s-seg) fine-tuned on [302 annotated IFCB images](https://huggingface.co/datasets/patcdaniel/pseudo-nitzschia-cell-segmentation).
app.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pseudo-nitzschia Cell Counter — Gradio Space
3
+ Drag-and-drop an IFCB image to count individual cells using YOLO11s instance segmentation.
4
+ """
5
+
6
+ import gradio as gr
7
+ import numpy as np
8
+ import cv2
9
+ from PIL import Image
10
+ from ultralytics import YOLO
11
+ from huggingface_hub import hf_hub_download
12
+ import matplotlib.pyplot as plt
13
+
14
+ # Download and load model at startup
15
+ MODEL_REPO = "patcdaniel/pseudo-nitzschia-yolo11s-seg"
16
+ model_path = hf_hub_download(MODEL_REPO, "best.pt")
17
+ model = YOLO(model_path)
18
+
19
+
20
+ def count_cells(input_image, confidence_threshold, iou_threshold):
21
+ """Run inference on an uploaded image and return annotated result with cell count."""
22
+ if input_image is None:
23
+ return None, "No image provided."
24
+
25
+ # Convert PIL to numpy BGR for YOLO
26
+ img_rgb = np.array(input_image)
27
+ if len(img_rgb.shape) == 2:
28
+ # Grayscale -> RGB
29
+ img_rgb = cv2.cvtColor(img_rgb, cv2.COLOR_GRAY2RGB)
30
+ elif img_rgb.shape[2] == 4:
31
+ # RGBA -> RGB
32
+ img_rgb = cv2.cvtColor(img_rgb, cv2.COLOR_RGBA2RGB)
33
+
34
+ # Run inference
35
+ results = model.predict(
36
+ source=img_rgb,
37
+ conf=confidence_threshold,
38
+ iou=iou_threshold,
39
+ imgsz=640,
40
+ verbose=False,
41
+ )
42
+
43
+ result = results[0]
44
+
45
+ # Check for detections
46
+ if result.masks is None or len(result.masks) == 0:
47
+ return input_image, "**0 cells detected.**"
48
+
49
+ masks = result.masks.data.cpu().numpy()
50
+ confidences = result.boxes.conf.cpu().numpy()
51
+ cell_count = len(masks)
52
+
53
+ # Create visualization
54
+ overlay = img_rgb.copy()
55
+ colors = plt.cm.rainbow(np.linspace(0, 1, cell_count))
56
+
57
+ polygons = []
58
+ areas = []
59
+ for i, mask in enumerate(masks):
60
+ mask_uint8 = (mask * 255).astype(np.uint8)
61
+ # Resize mask to image dimensions if needed
62
+ if mask_uint8.shape[:2] != img_rgb.shape[:2]:
63
+ mask_uint8 = cv2.resize(mask_uint8, (img_rgb.shape[1], img_rgb.shape[0]))
64
+
65
+ contours, _ = cv2.findContours(mask_uint8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
66
+ if contours:
67
+ largest = max(contours, key=cv2.contourArea)
68
+ epsilon = 0.001 * cv2.arcLength(largest, True)
69
+ poly = cv2.approxPolyDP(largest, epsilon, True)
70
+ color = (colors[i][:3] * 255).astype(np.uint8).tolist()
71
+
72
+ cv2.fillPoly(overlay, [poly], color)
73
+ cv2.polylines(img_rgb, [poly], True, color, 2)
74
+
75
+ area = cv2.contourArea(largest)
76
+ areas.append(area)
77
+ polygons.append(poly)
78
+
79
+ # Blend overlay
80
+ alpha = 0.3
81
+ annotated = cv2.addWeighted(overlay, alpha, img_rgb, 1 - alpha, 0)
82
+
83
+ # Add cell count label
84
+ text = f"Cells: {cell_count}"
85
+ font = cv2.FONT_HERSHEY_SIMPLEX
86
+ font_scale = max(0.6, min(img_rgb.shape[1] / 600, 1.5))
87
+ thickness = max(1, int(font_scale * 2))
88
+ (tw, th), _ = cv2.getTextSize(text, font, font_scale, thickness)
89
+ cv2.rectangle(annotated, (5, 5), (15 + tw, 15 + th), (255, 255, 255), -1)
90
+ cv2.putText(annotated, text, (10, 10 + th), font, font_scale, (0, 0, 200), thickness)
91
+
92
+ # Build summary text
93
+ avg_conf = float(np.mean(confidences))
94
+ avg_area = float(np.mean(areas)) if areas else 0
95
+
96
+ summary = (
97
+ f"### {cell_count} cell{'s' if cell_count != 1 else ''} detected\n\n"
98
+ f"| Metric | Value |\n"
99
+ f"|--------|-------|\n"
100
+ f"| Cell count | {cell_count} |\n"
101
+ f"| Avg. confidence | {avg_conf:.3f} |\n"
102
+ f"| Confidence range | {float(confidences.min()):.3f} – {float(confidences.max()):.3f} |\n"
103
+ f"| Avg. mask area (px) | {avg_area:.1f} |\n"
104
+ )
105
+
106
+ return Image.fromarray(annotated), summary
107
+
108
+
109
+ # Build Gradio interface
110
+ with gr.Blocks(title="Pseudo-nitzschia Cell Counter") as demo:
111
+ gr.Markdown(
112
+ "# Pseudo-nitzschia Cell Counter\n"
113
+ "Upload an IFCB image of a *Pseudo-nitzschia* chain colony to count individual cells "
114
+ "using [YOLO11s instance segmentation](https://huggingface.co/patcdaniel/pseudo-nitzschia-yolo11s-seg)."
115
+ )
116
+
117
+ with gr.Row():
118
+ with gr.Column():
119
+ input_image = gr.Image(type="pil", label="Upload Image")
120
+ with gr.Row():
121
+ conf_slider = gr.Slider(
122
+ minimum=0.05, maximum=0.95, value=0.25, step=0.05,
123
+ label="Confidence Threshold"
124
+ )
125
+ iou_slider = gr.Slider(
126
+ minimum=0.1, maximum=0.95, value=0.7, step=0.05,
127
+ label="IoU Threshold (NMS)"
128
+ )
129
+ run_btn = gr.Button("Count Cells", variant="primary")
130
+
131
+ with gr.Column():
132
+ output_image = gr.Image(type="pil", label="Segmentation Result")
133
+ output_text = gr.Markdown(label="Results")
134
+
135
+ run_btn.click(
136
+ fn=count_cells,
137
+ inputs=[input_image, conf_slider, iou_slider],
138
+ outputs=[output_image, output_text],
139
+ )
140
+
141
+ gr.Markdown(
142
+ "---\n"
143
+ "**Model:** [patcdaniel/pseudo-nitzschia-yolo11s-seg](https://huggingface.co/patcdaniel/pseudo-nitzschia-yolo11s-seg) | "
144
+ "**Dataset:** [patcdaniel/pseudo-nitzschia-cell-segmentation](https://huggingface.co/datasets/patcdaniel/pseudo-nitzschia-cell-segmentation)"
145
+ )
146
+
147
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ ultralytics>=8.0.0
2
+ huggingface_hub>=0.20.0
3
+ numpy>=1.24.0
4
+ opencv-python-headless>=4.8.0
5
+ pillow>=10.0.0