ColdSlim commited on
Commit
fc0a615
·
verified ·
1 Parent(s): 24b8eef

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -217
app.py CHANGED
@@ -1,246 +1,205 @@
1
- import spaces
 
 
 
 
 
 
 
 
 
 
 
 
2
  import gradio as gr
 
3
  import torch
4
- from transformers import AutoProcessor, Qwen2VLForConditionalGeneration
5
  from PIL import Image
6
- import logging
7
- import subprocess
8
- import sys
9
 
10
- logging.basicConfig(level=logging.INFO)
 
 
 
 
11
  logger = logging.getLogger(__name__)
12
 
13
 
14
- # Configure logging
15
- logging.basicConfig(level=logging.INFO)
16
- logger = logging.getLogger(__name__)
 
 
17
 
18
- # Global variables for model and processor
19
- model = None
20
- processor = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- def load_model():
23
- """Load the fine-tuned dermatology model"""
24
- global model, processor
25
-
26
  try:
27
- # Load the merged model (replace with your actual model path)
28
- model_name = "ColdSlim/Dermatology-Qwen2.5-VL-3B" # Update with your actual model name
29
-
30
- logger.info(f"Loading model: {model_name}")
31
- processor = AutoProcessor.from_pretrained(model_name, trust_remote_code=True)
32
  model = Qwen2VLForConditionalGeneration.from_pretrained(
33
- model_name,
34
- dtype=torch.bfloat16,
35
- device_map="auto",
36
  trust_remote_code=True,
37
  low_cpu_mem_usage=True,
38
- ignore_mismatched_sizes=True
39
  )
40
-
41
  logger.info("Model loaded successfully!")
42
- return True
43
-
44
- except Exception as e:
45
- logger.error(f"Error loading model: {e}")
46
- return False
47
-
48
- def analyze_skin_condition(image, question="Describe this skin condition in detail."):
49
- """Analyze skin condition from uploaded image"""
50
- global model, processor
51
-
52
- if model is None or processor is None:
53
- return "❌ Model not loaded. Please wait for the model to load or contact the administrator."
54
-
55
- if image is None:
56
- return "❌ Please upload an image first."
57
-
58
- try:
59
- # Prepare the conversation
60
- messages = [
61
- {
62
- "role": "user",
63
- "content": [
64
- {"type": "image", "image": image},
65
- {"type": "text", "text": question}
66
- ]
67
- }
68
- ]
69
-
70
- # Process the input
71
- text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
72
- image_inputs, video_inputs = processor.process_vision_info(messages)
73
-
74
- inputs = processor(
75
- text=[text],
76
- images=image_inputs,
77
- videos=video_inputs,
78
- padding=True,
79
- return_tensors="pt"
80
- )
81
-
82
- # Move inputs to the same device as model
83
- inputs = {k: v.to(model.device) if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
84
-
85
- # Generate response
86
  with torch.no_grad():
87
- generated_ids = model.generate(
88
  **inputs,
89
- max_new_tokens=512,
90
- do_sample=True,
91
- temperature=0.7,
92
- top_p=0.9,
93
- pad_token_id=processor.tokenizer.eos_token_id
94
  )
95
-
96
- # Decode the response
97
- generated_ids_trimmed = [
98
- out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
99
  ]
100
- output_text = processor.batch_decode(
101
- generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
102
  )[0]
103
-
104
- return output_text
105
-
 
 
 
 
106
  except Exception as e:
107
- logger.error(f"Error during inference: {e}")
108
- return f"❌ Error analyzing image: {str(e)}"
109
-
110
- def create_interface():
111
- """Create the Gradio interface"""
112
-
113
- # Load model on startup
114
- model_loaded = load_model()
115
-
116
- with gr.Blocks(
117
- title="Dermatology AI Assistant",
118
- theme=gr.themes.Soft(),
119
- css="""
120
- .gradio-container {
121
- max-width: 1200px !important;
122
- margin: auto !important;
123
- }
124
- .main-header {
125
- text-align: center;
126
- margin-bottom: 2rem;
127
- }
128
- .warning-box {
129
- background-color: #fff3cd;
130
- border: 1px solid #ffeaa7;
131
- border-radius: 8px;
132
- padding: 1rem;
133
- margin: 1rem 0;
134
- }
135
- """
136
- ) as demo:
137
-
138
- gr.HTML("""
139
- <div class="main-header">
140
- <h1>🩺 Dermatology AI Assistant</h1>
141
- <p>Powered by Qwen2.5-VL-3B fine-tuned for dermatology analysis</p>
142
- </div>
143
- """)
144
-
145
- # Warning message
146
- gr.HTML("""
147
- <div class="warning-box">
148
- <h3>⚠️ Medical Disclaimer</h3>
149
- <p>This AI assistant is for educational and research purposes only.
150
- It should not be used as a substitute for professional medical advice,
151
- diagnosis, or treatment. Always consult with a qualified healthcare
152
- provider for medical concerns.</p>
153
- </div>
154
- """)
155
-
156
- with gr.Row():
157
- with gr.Column(scale=1):
158
- # Image upload
159
- image_input = gr.Image(
160
- label="Upload Skin Image",
161
- type="pil",
162
- height=400
163
- )
164
-
165
- # Question input
166
- question_input = gr.Textbox(
167
- label="Question (Optional)",
168
- placeholder="Describe this skin condition in detail.",
169
- value="Describe this skin condition in detail.",
170
- lines=3
171
- )
172
-
173
- # Analyze button
174
- analyze_btn = gr.Button(
175
- "🔍 Analyze Skin Condition",
176
- variant="primary",
177
- size="lg"
178
- )
179
-
180
- # Example questions
181
- gr.HTML("""
182
- <h4>💡 Example Questions:</h4>
183
- <ul>
184
- <li>What type of skin condition is this?</li>
185
- <li>Describe the characteristics of this lesion.</li>
186
- <li>What are the potential causes of this skin issue?</li>
187
- <li>What should I know about this skin condition?</li>
188
- </ul>
189
- """)
190
-
191
- with gr.Column(scale=1):
192
- # Output
193
- output_text = gr.Textbox(
194
- label="AI Analysis",
195
- lines=15,
196
- max_lines=20,
197
- show_copy_button=True
198
- )
199
-
200
- # Examples
201
- gr.Examples(
202
- examples=[
203
- ["What type of skin condition is this?", "Describe this skin condition in detail."],
204
- ["What are the characteristics of this lesion?", "Describe this skin condition in detail."],
205
- ["What should I know about this skin issue?", "Describe this skin condition in detail."],
206
- ],
207
- inputs=[question_input, question_input],
208
- label="💡 Example Questions"
209
  )
210
-
211
- # Event handlers
212
- analyze_btn.click(
213
- fn=analyze_skin_condition,
214
- inputs=[image_input, question_input],
215
- outputs=output_text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  )
217
-
218
- # Model status
219
- if model_loaded:
220
- gr.HTML("<div style='text-align: center; color: green;'>✅ Model loaded successfully!</div>")
221
- else:
222
- gr.HTML("<div style='text-align: center; color: red;'>❌ Model loading failed. Please check the logs.</div>")
223
-
224
  return demo
225
 
226
- @spaces.GPU
227
  def main():
228
- """Main function with GPU decorator for Hugging Face Spaces"""
229
- try:
230
- # Create and launch the interface
231
- demo = create_interface()
232
- demo.launch(
233
- server_name="0.0.0.0",
234
- server_port=7860,
235
- share=False,
236
- show_error=True,
237
- inbrowser=False,
238
- quiet=False
239
- )
240
- except Exception as e:
241
- logger.error(f"Error launching app: {e}")
242
- raise
243
 
244
  if __name__ == "__main__":
245
  main()
246
-
 
1
+ # app.py
2
+ # Dermatology-AI-Assistant — Hugging Face Space (ZeroGPU-ready)
3
+ # - Logging is configured before use
4
+ # - No runtime pip installs (use requirements.txt)
5
+ # - ZeroGPU acquired only during inference via @spaces.GPU
6
+ # - Uses qwen-vl-utils.process_vision_info (fixes missing attribute error)
7
+ # - SSR disabled in Gradio launch to avoid Node 20 requirement in container
8
+
9
+ import os
10
+ import sys
11
+ import logging
12
+ from typing import Optional, Tuple
13
+
14
  import gradio as gr
15
+ import spaces
16
  import torch
 
17
  from PIL import Image
18
+ from transformers import AutoProcessor, Qwen2VLForConditionalGeneration
19
+ from qwen_vl_utils import process_vision_info
 
20
 
21
+
22
+ # ---------------------------
23
+ # Logging
24
+ # ---------------------------
25
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s:%(name)s:%(message)s")
26
  logger = logging.getLogger(__name__)
27
 
28
 
29
+ # ---------------------------
30
+ # Config
31
+ # ---------------------------
32
+ # Fine-tuned (or partially fine-tuned) Qwen VL checkpoint
33
+ MODEL_ID = os.environ.get("MODEL_ID", "ColdSlim/Dermatology-Qwen2.5-VL-3B")
34
 
35
+ # Generation params (tweak as needed)
36
+ GEN_KW = dict(
37
+ max_new_tokens=512,
38
+ do_sample=True,
39
+ temperature=0.7,
40
+ top_p=0.9,
41
+ )
42
+
43
+ # ZeroGPU time (seconds). Increase if your model is slow to generate.
44
+ ZGPU_DURATION = int(os.environ.get("ZGPU_DURATION", "180"))
45
+
46
+ # Preload only the processor on CPU; load the model inside GPU-decorated call.
47
+ logger.info(f"Loading processor from: {MODEL_ID}")
48
+ processor = AutoProcessor.from_pretrained(MODEL_ID, trust_remote_code=True)
49
+ logger.info("Processor loaded.")
50
+
51
+
52
+ # ---------------------------
53
+ # Helpers
54
+ # ---------------------------
55
+ def build_inputs(image: Image.Image, question: str):
56
+ """
57
+ Build Qwen-style multimodal chat inputs using qwen-vl-utils.
58
+ Returns a dict of tensors ready for model.generate.
59
+ """
60
+ messages = [
61
+ {
62
+ "role": "user",
63
+ "content": [
64
+ {"type": "image", "image": image},
65
+ {"type": "text", "text": question},
66
+ ],
67
+ }
68
+ ]
69
+
70
+ # Chat template
71
+ text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
72
+
73
+ # Vision inputs
74
+ image_inputs, video_inputs = process_vision_info(messages)
75
+
76
+ # Pack tensors (CPU for now; we move to CUDA later)
77
+ inputs = processor(
78
+ text=[text],
79
+ images=image_inputs,
80
+ videos=video_inputs,
81
+ padding=True,
82
+ return_tensors="pt",
83
+ )
84
+ return inputs
85
+
86
+
87
+ def format_derm_disclaimer(ans: str) -> str:
88
+ """Append a short medical disclaimer (non-blocking)."""
89
+ tail = (
90
+ "\n\n---\n"
91
+ "_Disclaimer: This AI is not a medical device. The output is informational and may be inaccurate. "
92
+ "Consult a qualified dermatologist for diagnosis and treatment._"
93
+ )
94
+ return ans + tail
95
+
96
+
97
+ # ---------------------------
98
+ # Inference (ZeroGPU)
99
+ # ---------------------------
100
+ @spaces.GPU(duration=ZGPU_DURATION)
101
+ def analyze_skin_condition(image: Optional[Image.Image], question: str) -> str:
102
+ """
103
+ Main inference function. Runs inside a ZeroGPU reservation window.
104
+ Loads model on GPU, generates, frees VRAM.
105
+ """
106
+ if image is None:
107
+ return "❌ Please upload an image first."
108
 
 
 
 
 
109
  try:
110
+ logger.info(f"Loading model on GPU: {MODEL_ID}")
111
+ # On ZeroGPU, load inside the GPU-decorated function
 
 
 
112
  model = Qwen2VLForConditionalGeneration.from_pretrained(
113
+ MODEL_ID,
114
+ torch_dtype=torch.float16, # fp16 is broadly compatible on ZeroGPU
115
+ device_map="cuda", # place modules on available CUDA
116
  trust_remote_code=True,
117
  low_cpu_mem_usage=True,
118
+ ignore_mismatched_sizes=True, # your logs indicated shape diffs; keep this to avoid crash
119
  )
 
120
  logger.info("Model loaded successfully!")
121
+
122
+ # Build and move inputs to CUDA
123
+ inputs = build_inputs(image, question)
124
+ inputs = {k: v.to("cuda") if isinstance(v, torch.Tensor) else v for k, v in inputs.items()}
125
+
126
+ # Generate
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  with torch.no_grad():
128
+ out_ids = model.generate(
129
  **inputs,
130
+ **GEN_KW,
131
+ pad_token_id=processor.tokenizer.eos_token_id,
 
 
 
132
  )
133
+
134
+ # Strip prompt tokens before decoding for clean answer
135
+ prompt_len_trimmed = [
136
+ out[len(inp):] for inp, out in zip(inputs["input_ids"], out_ids)
137
  ]
138
+ text = processor.batch_decode(
139
+ prompt_len_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
140
  )[0]
141
+
142
+ # Free VRAM early
143
+ del model
144
+ torch.cuda.empty_cache()
145
+
146
+ return format_derm_disclaimer(text)
147
+
148
  except Exception as e:
149
+ logger.exception("Error during inference")
150
+ return f"❌ Error analyzing image: {e}"
151
+
152
+
153
+ # ---------------------------
154
+ # UI
155
+ # ---------------------------
156
+ def create_interface() -> gr.Blocks:
157
+ with gr.Blocks(title="Dermatology AI Assistant") as demo:
158
+ gr.Markdown(
159
+ "# Dermatology AI Assistant\n"
160
+ "Upload a skin photo and ask a question. The model will provide an informational response."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
  )
162
+
163
+ with gr.Row():
164
+ image_input = gr.Image(type="pil", label="Upload Image (JPG/PNG)")
165
+ question_input = gr.Textbox(
166
+ label="Question / Prompt",
167
+ value="Describe this skin condition in detail and suggest possible next steps.",
168
+ lines=3,
169
+ )
170
+
171
+ with gr.Row():
172
+ submit_btn = gr.Button("Analyze", variant="primary")
173
+ clear_btn = gr.Button("Clear")
174
+
175
+ output_box = gr.Textbox(label="Response", lines=16)
176
+
177
+ # Wire events
178
+ submit_btn.click(fn=analyze_skin_condition, inputs=[image_input, question_input], outputs=output_box, queue=True)
179
+ clear_btn.click(fn=lambda: (None, ""), inputs=None, outputs=[image_input, question_input])
180
+
181
+ # Queue for concurrency control (ZeroGPU friendly)
182
+ demo.queue(concurrency_count=1, status_update_rate=1)
183
+
184
+ gr.Markdown(
185
+ "Tips: Ensure good lighting and focus. Avoid uploading personally identifying information."
186
  )
187
+
 
 
 
 
 
 
188
  return demo
189
 
190
+
191
  def main():
192
+ demo = create_interface()
193
+ demo.launch(
194
+ server_name="0.0.0.0",
195
+ server_port=7860,
196
+ share=False,
197
+ show_error=True,
198
+ inbrowser=False,
199
+ quiet=False,
200
+ ssr_mode=False, # disable SSR to avoid Node 20 requirement in Spaces container
201
+ )
202
+
 
 
 
 
203
 
204
  if __name__ == "__main__":
205
  main()