Rjavenger commited on
Commit
41c9fff
·
verified ·
1 Parent(s): 5ef078f

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +23 -0
  2. app.py +122 -0
  3. index.html +258 -0
  4. requirements.txt +9 -0
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ libgl1 \
7
+ libglib2.0-0 \
8
+ wget \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Download Models
15
+ RUN mkdir -p weights && \
16
+ wget -O weights/yolo12_cls.pt https://huggingface.co/Subh775/Yolo12-cls.latest_ep15.bsl/resolve/main/weights/best.pt && \
17
+ mkdir -p /tmp && \
18
+ wget -O /tmp/checkpoint_best_total.pth https://huggingface.co/Subh775/Seg-Basil-rfdetr/resolve/main/checkpoint_best_total.pth
19
+
20
+ COPY . .
21
+
22
+ EXPOSE 7860
23
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import base64
4
+ import gc
5
+ import torch
6
+ import numpy as np
7
+ from fastapi import FastAPI, HTTPException
8
+ from fastapi.responses import HTMLResponse
9
+ from pydantic import BaseModel
10
+ from PIL import Image
11
+ from ultralytics import YOLO
12
+ import supervision as sv
13
+
14
+ # Environment setup for CPU efficiency
15
+ os.environ["CUDA_VISIBLE_DEVICES"] = ""
16
+ os.environ["OMP_NUM_THREADS"] = "4"
17
+ torch.set_num_threads(4)
18
+
19
+ # Import local RF-DETR wrapper (assuming the library is installed)
20
+ from rfdetr import RFDETRSegPreview
21
+
22
+ app = FastAPI()
23
+
24
+ # Model paths and Globals
25
+ SEG_MODEL_PATH = "/tmp/checkpoint_best_total.pth"
26
+ CLS_MODEL_PATH = "weights/yolo12_cls.pt"
27
+
28
+ models = {"seg": None, "cls": None}
29
+
30
+ def load_models():
31
+ if models["seg"] is None:
32
+ # RF-DETR Initialization
33
+ models["seg"] = RFDETRSegPreview(pretrain_weights=SEG_MODEL_PATH)
34
+ models["seg"].optimize_for_inference()
35
+ if models["cls"] is None:
36
+ # YOLO12-cls Initialization
37
+ models["cls"] = YOLO(CLS_MODEL_PATH)
38
+
39
+ class PredictionConfig(BaseModel):
40
+ image: str
41
+ seg_enabled: bool
42
+ seg_conf: float
43
+ seg_show_conf: bool
44
+ cls_enabled: bool
45
+ cls_show_conf: bool
46
+ cls_show_label: bool
47
+
48
+ @app.get("/", response_class=HTMLResponse)
49
+ async def serve_ui():
50
+ with open("index.html", "r") as f:
51
+ return f.read()
52
+
53
+ @app.post("/predict")
54
+ async def predict(config: PredictionConfig):
55
+ load_models()
56
+
57
+ try:
58
+ # Decode image
59
+ header, encoded = config.image.split(",", 1) if "," in config.image else (None, config.image)
60
+ img_bytes = base64.b64decode(encoded)
61
+ original_img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
62
+
63
+ # 1. Segmentation Phase
64
+ detections = models["seg"].predict(original_img, threshold=config.seg_conf)
65
+
66
+ if len(detections) == 0:
67
+ return {"annotated": config.image, "count": 0}
68
+
69
+ # 2. Classification Phase (if enabled)
70
+ labels = []
71
+ if config.cls_enabled:
72
+ for i in range(len(detections.xyxy)):
73
+ x1, y1, x2, y2 = detections.xyxy[i].astype(int)
74
+ crop = original_img.crop((x1, y1, x2, y2))
75
+ cls_res = models["cls"](crop)[0]
76
+
77
+ top1_idx = cls_res.probs.top1
78
+ name = cls_res.names[top1_idx]
79
+ conf = float(cls_res.probs.top1conf)
80
+
81
+ label_str = ""
82
+ if config.cls_show_label: label_str += f"{name} "
83
+ if config.cls_show_conf: label_str += f"{conf:.2f}"
84
+ labels.append(label_str.strip())
85
+ else:
86
+ # Fallback to generic labels or segmentation conf
87
+ for conf in detections.confidence:
88
+ labels.append(f"Leaf {conf:.2f}" if config.seg_show_conf else "Leaf")
89
+
90
+ # 3. Annotation Phase
91
+ palette = sv.ColorPalette.from_hex(["#EA782D", "#FF7A5A", "#FFA382"])
92
+ mask_annotator = sv.MaskAnnotator(color=palette)
93
+ label_annotator = sv.LabelAnnotator(
94
+ color=palette,
95
+ text_position=sv.Position.CENTER_OF_MASS,
96
+ text_scale=0.5
97
+ )
98
+
99
+ annotated_img = original_img.copy()
100
+ if config.seg_enabled:
101
+ annotated_img = mask_annotator.annotate(scene=annotated_img, detections=detections)
102
+
103
+ annotated_img = label_annotator.annotate(scene=annotated_img, detections=detections, labels=labels)
104
+
105
+ # Encode result
106
+ buffered = io.BytesIO()
107
+ annotated_img.save(buffered, format="PNG")
108
+ encoded_res = base64.b64encode(buffered.getvalue()).decode("ascii")
109
+
110
+ return {
111
+ "annotated": f"data:image/png;base64,{encoded_res}",
112
+ "count": len(detections)
113
+ }
114
+
115
+ except Exception as e:
116
+ raise HTTPException(status_code=500, detail=str(e))
117
+ finally:
118
+ gc.collect()
119
+
120
+ if __name__ == "__main__":
121
+ import uvicorn
122
+ uvicorn.run(app, host="0.0.0.0", port=7860)
index.html ADDED
@@ -0,0 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Tulsi Analytics — DETR & YOLO12</title>
7
+ <style>
8
+ :root {
9
+ --primary-orange: #EA782D;
10
+ --accent-orange: #FF7A5A;
11
+ --dark-bg: #121212;
12
+ --surface: #1E1E2E;
13
+ --text: #E0E0E0;
14
+ --border: rgba(255, 255, 255, 0.1);
15
+ }
16
+
17
+ body {
18
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
19
+ background: var(--dark-bg);
20
+ color: var(--text);
21
+ margin: 0;
22
+ padding: 20px;
23
+ }
24
+
25
+ .container {
26
+ max-width: 1400px;
27
+ margin: 0 auto;
28
+ background: var(--surface);
29
+ border-radius: 12px;
30
+ border: 1px solid var(--border);
31
+ overflow: hidden;
32
+ }
33
+
34
+ header {
35
+ padding: 25px;
36
+ text-align: center;
37
+ border-bottom: 1px solid var(--border);
38
+ background: rgba(0,0,0,0.2);
39
+ }
40
+
41
+ h1 { color: var(--primary-orange); margin: 0; font-weight: 500; }
42
+
43
+ .layout {
44
+ display: grid;
45
+ grid-template-columns: 1fr 1fr;
46
+ gap: 20px;
47
+ padding: 20px;
48
+ }
49
+
50
+ @media (max-width: 1000px) {
51
+ .layout { grid-template-columns: 1fr; }
52
+ }
53
+
54
+ .panel {
55
+ background: rgba(0,0,0,0.15);
56
+ padding: 20px;
57
+ border-radius: 10px;
58
+ }
59
+
60
+ .image-view {
61
+ width: 100%;
62
+ height: 450px;
63
+ background: #000;
64
+ border-radius: 8px;
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ margin-bottom: 15px;
69
+ border: 1px solid var(--border);
70
+ overflow: hidden;
71
+ }
72
+
73
+ img { max-width: 100%; max-height: 100%; object-fit: contain; }
74
+
75
+ .controls {
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: 15px;
79
+ }
80
+
81
+ .control-row {
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: space-between;
85
+ padding: 10px;
86
+ background: rgba(255,255,255,0.03);
87
+ border-radius: 6px;
88
+ }
89
+
90
+ .slider-group { padding: 10px; }
91
+ input[type="range"] { width: 100%; accent-color: var(--primary-orange); }
92
+
93
+ /* Toggle Switch */
94
+ .switch {
95
+ position: relative;
96
+ display: inline-block;
97
+ width: 44px;
98
+ height: 22px;
99
+ }
100
+ .switch input { opacity: 0; width: 0; height: 0; }
101
+ .slider {
102
+ position: absolute;
103
+ cursor: pointer;
104
+ top: 0; left: 0; right: 0; bottom: 0;
105
+ background-color: #444;
106
+ transition: .4s;
107
+ border-radius: 34px;
108
+ }
109
+ .slider:before {
110
+ position: absolute;
111
+ content: "";
112
+ height: 16px; width: 16px;
113
+ left: 3px; bottom: 3px;
114
+ background-color: white;
115
+ transition: .4s;
116
+ border-radius: 50%;
117
+ }
118
+ input:checked + .slider { background-color: var(--primary-orange); }
119
+ input:checked + .slider:before { transform: translateX(22px); }
120
+
121
+ .predict-btn {
122
+ background: var(--primary-orange);
123
+ color: white;
124
+ border: none;
125
+ padding: 15px;
126
+ border-radius: 8px;
127
+ font-weight: 600;
128
+ cursor: pointer;
129
+ transition: 0.2s;
130
+ width: 100%;
131
+ }
132
+ .predict-btn:hover { background: var(--accent-orange); }
133
+ .predict-btn:disabled { opacity: 0.5; cursor: not-allowed; }
134
+
135
+ .upload-box {
136
+ border: 2px dashed var(--border);
137
+ padding: 20px;
138
+ text-align: center;
139
+ cursor: pointer;
140
+ margin-bottom: 15px;
141
+ }
142
+ </style>
143
+ </head>
144
+ <body>
145
+
146
+ <div class="container">
147
+ <header>
148
+ <h1>Tulsi Disease Analyzer</h1>
149
+ </header>
150
+
151
+ <div class="layout">
152
+ <div class="panel">
153
+ <div class="upload-box" id="drop-zone">Click or Drop Image Here</div>
154
+ <input type="file" id="file-input" hidden accept="image/*">
155
+
156
+ <div class="image-view">
157
+ <img id="input-preview" src="" style="display:none;">
158
+ </div>
159
+
160
+ <div class="controls">
161
+ <div class="control-row">
162
+ <span>Enable Segmentation</span>
163
+ <label class="switch"><input type="checkbox" id="seg-enable" checked><span class="slider"></span></label>
164
+ </div>
165
+ <div class="slider-group">
166
+ <label>Seg. Confidence: <span id="seg-val">0.25</span></label>
167
+ <input type="range" id="seg-conf" min="5" max="95" value="25">
168
+ </div>
169
+ <div class="control-row">
170
+ <span>Show Seg. Confidence</span>
171
+ <label class="switch"><input type="checkbox" id="seg-show-conf"><span class="slider"></span></label>
172
+ </div>
173
+
174
+ <div class="control-row">
175
+ <span>Enable Classification</span>
176
+ <label class="switch"><input type="checkbox" id="cls-enable" checked><span class="slider"></span></label>
177
+ </div>
178
+ <div class="control-row">
179
+ <span>Show Label</span>
180
+ <label class="switch"><input type="checkbox" id="cls-show-label" checked><span class="slider"></span></label>
181
+ </div>
182
+ <div class="control-row">
183
+ <span>Show Cls. Confidence</span>
184
+ <label class="switch"><input type="checkbox" id="cls-show-conf" checked><span class="slider"></span></label>
185
+ </div>
186
+
187
+ <button id="predict-btn" class="predict-btn" disabled>Run Analysis</button>
188
+ </div>
189
+ </div>
190
+
191
+ <div class="panel">
192
+ <h3 style="margin-top:0;">Analysis Result</h3>
193
+ <div class="image-view">
194
+ <img id="output-preview" src="">
195
+ </div>
196
+ <div id="status">Status: Waiting for input</div>
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ <script>
202
+ const fileInput = document.getElementById('file-input');
203
+ const dropZone = document.getElementById('drop-zone');
204
+ const inputImg = document.getElementById('input-preview');
205
+ const outputImg = document.getElementById('output-preview');
206
+ const predictBtn = document.getElementById('predict-btn');
207
+ const segConf = document.getElementById('seg-conf');
208
+ const segVal = document.getElementById('seg-val');
209
+
210
+ segConf.oninput = () => segVal.innerText = (segConf.value / 100).toFixed(2);
211
+
212
+ dropZone.onclick = () => fileInput.click();
213
+ fileInput.onchange = (e) => handleFile(e.target.files[0]);
214
+
215
+ function handleFile(file) {
216
+ if (!file) return;
217
+ const reader = new FileReader();
218
+ reader.onload = (e) => {
219
+ inputImg.src = e.target.result;
220
+ inputImg.style.display = 'block';
221
+ predictBtn.disabled = false;
222
+ };
223
+ reader.readAsDataURL(file);
224
+ }
225
+
226
+ predictBtn.onclick = async () => {
227
+ predictBtn.disabled = true;
228
+ predictBtn.innerText = "Processing...";
229
+
230
+ const payload = {
231
+ image: inputImg.src,
232
+ seg_enabled: document.getElementById('seg-enable').checked,
233
+ seg_conf: segConf.value / 100,
234
+ seg_show_conf: document.getElementById('seg-show-conf').checked,
235
+ cls_enabled: document.getElementById('cls-enable').checked,
236
+ cls_show_conf: document.getElementById('cls-show-conf').checked,
237
+ cls_show_label: document.getElementById('cls-show-label').checked
238
+ };
239
+
240
+ try {
241
+ const resp = await fetch('/predict', {
242
+ method: 'POST',
243
+ headers: {'Content-Type': 'application/json'},
244
+ body: JSON.stringify(payload)
245
+ });
246
+ const data = await resp.json();
247
+ outputImg.src = data.annotated;
248
+ document.getElementById('status').innerText = `Status: Detected ${data.count} leaves`;
249
+ } catch (e) {
250
+ alert("Error running inference");
251
+ } finally {
252
+ predictBtn.disabled = false;
253
+ predictBtn.innerText = "Run Analysis";
254
+ }
255
+ };
256
+ </script>
257
+ </body>
258
+ </html>
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ ultralytics
4
+ supervision
5
+ pillow
6
+ numpy
7
+ torch
8
+ requests
9
+ rfdetr