Spaces:
Sleeping
Sleeping
luxmorocco
commited on
Commit
β’
57fe26d
1
Parent(s):
9bcda32
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from streamlit_drawable_canvas import st_canvas
|
3 |
+
from PIL import Image, ImageDraw, ImageFont
|
4 |
+
import numpy as np
|
5 |
+
import json
|
6 |
+
import io
|
7 |
+
from datetime import datetime
|
8 |
+
import os
|
9 |
+
|
10 |
+
def initialize_session_state():
|
11 |
+
"""Initialize session state variables"""
|
12 |
+
if "annotations" not in st.session_state:
|
13 |
+
st.session_state["annotations"] = []
|
14 |
+
if "current_tool" not in st.session_state:
|
15 |
+
st.session_state["current_tool"] = "rect"
|
16 |
+
if "annotation_history" not in st.session_state:
|
17 |
+
st.session_state["annotation_history"] = []
|
18 |
+
if "is_authenticated" not in st.session_state:
|
19 |
+
st.session_state["is_authenticated"] = False
|
20 |
+
|
21 |
+
def authenticate_user():
|
22 |
+
"""Handle user authentication"""
|
23 |
+
st.title("π₯ Alyse AI Prescription Annotation Tool")
|
24 |
+
|
25 |
+
if not st.session_state["is_authenticated"]:
|
26 |
+
with st.form("login_form"):
|
27 |
+
st.write("Please enter your credentials to access the tool.")
|
28 |
+
username = st.text_input("Username:")
|
29 |
+
password = st.text_input("Password:", type="password")
|
30 |
+
submit = st.form_submit_button("Login")
|
31 |
+
|
32 |
+
if submit:
|
33 |
+
if username == "alyse" and password == "pharmacie":
|
34 |
+
st.session_state["is_authenticated"] = True
|
35 |
+
st.success("β
Access granted! You can now use the application.")
|
36 |
+
st.rerun()
|
37 |
+
else:
|
38 |
+
st.error("β Invalid credentials. Please try again.")
|
39 |
+
return False
|
40 |
+
return True
|
41 |
+
|
42 |
+
def create_sidebar_controls():
|
43 |
+
"""Create sidebar controls for annotation settings"""
|
44 |
+
st.sidebar.header("π Annotation Controls")
|
45 |
+
|
46 |
+
# Tool selection
|
47 |
+
tool_options = {
|
48 |
+
"rect": "Rectangle Box",
|
49 |
+
"line": "Line",
|
50 |
+
"circle": "Circle",
|
51 |
+
"freedraw": "Free Draw"
|
52 |
+
}
|
53 |
+
st.session_state["current_tool"] = st.sidebar.radio(
|
54 |
+
"Select Drawing Tool:",
|
55 |
+
options=list(tool_options.keys()),
|
56 |
+
format_func=lambda x: tool_options[x]
|
57 |
+
)
|
58 |
+
|
59 |
+
# Color selection
|
60 |
+
stroke_color = st.sidebar.color_picker("Stroke Color:", "#0000FF")
|
61 |
+
stroke_width = st.sidebar.slider("Stroke Width:", 1, 10, 2)
|
62 |
+
|
63 |
+
# Annotation categories
|
64 |
+
annotation_category = st.sidebar.selectbox(
|
65 |
+
"Annotation Category:",
|
66 |
+
["Medication Name", "Dosage", "Frequency", "Duration", "Patient Info", "Doctor Info", "Other"]
|
67 |
+
)
|
68 |
+
|
69 |
+
return stroke_color, stroke_width, annotation_category
|
70 |
+
|
71 |
+
def preprocess_image(image):
|
72 |
+
"""Resize image if too large for Streamlit Cloud"""
|
73 |
+
max_size = (800, 800) # Maximum dimensions
|
74 |
+
|
75 |
+
# Calculate aspect ratio
|
76 |
+
width_ratio = max_size[0] / image.size[0]
|
77 |
+
height_ratio = max_size[1] / image.size[1]
|
78 |
+
resize_ratio = min(width_ratio, height_ratio)
|
79 |
+
|
80 |
+
# Only resize if image is too large
|
81 |
+
if resize_ratio < 1:
|
82 |
+
new_size = (
|
83 |
+
int(image.size[0] * resize_ratio),
|
84 |
+
int(image.size[1] * resize_ratio)
|
85 |
+
)
|
86 |
+
return image.resize(new_size, Image.Resampling.LANCZOS)
|
87 |
+
return image
|
88 |
+
|
89 |
+
def handle_canvas_drawing(image, stroke_color, stroke_width, category):
|
90 |
+
"""Handle canvas drawing with preprocessed image"""
|
91 |
+
processed_image = preprocess_image(image)
|
92 |
+
|
93 |
+
canvas_result = st_canvas(
|
94 |
+
fill_color="rgba(0, 0, 0, 0)",
|
95 |
+
stroke_width=stroke_width,
|
96 |
+
stroke_color=stroke_color,
|
97 |
+
background_image=processed_image,
|
98 |
+
update_streamlit=True,
|
99 |
+
width=processed_image.size[0],
|
100 |
+
height=processed_image.size[1],
|
101 |
+
drawing_mode=st.session_state["current_tool"],
|
102 |
+
display_toolbar=True,
|
103 |
+
key="canvas",
|
104 |
+
)
|
105 |
+
|
106 |
+
if canvas_result.json_data:
|
107 |
+
objects = canvas_result.json_data.get("objects", [])
|
108 |
+
for obj in objects:
|
109 |
+
if obj not in [ann.get("object_data") for ann in st.session_state["annotations"]]:
|
110 |
+
new_annotation = {
|
111 |
+
"object_data": obj,
|
112 |
+
"category": category,
|
113 |
+
"text": "",
|
114 |
+
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
115 |
+
}
|
116 |
+
st.session_state["annotations"].append(new_annotation)
|
117 |
+
st.session_state["annotation_history"].append(new_annotation)
|
118 |
+
|
119 |
+
|
120 |
+
def display_annotation_list():
|
121 |
+
"""Display and manage list of annotations"""
|
122 |
+
st.sidebar.subheader("π Current Annotations")
|
123 |
+
|
124 |
+
for i, annotation in enumerate(st.session_state["annotations"]):
|
125 |
+
with st.sidebar.expander(f"Annotation {i+1} - {annotation['category']}"):
|
126 |
+
# Update annotation text
|
127 |
+
new_text = st.text_area(
|
128 |
+
"Description:",
|
129 |
+
annotation["text"],
|
130 |
+
key=f"text_input_{i}"
|
131 |
+
)
|
132 |
+
st.session_state["annotations"][i]["text"] = new_text
|
133 |
+
|
134 |
+
# Display annotation details
|
135 |
+
st.write(f"Created: {annotation['timestamp']}")
|
136 |
+
|
137 |
+
# Delete individual annotation
|
138 |
+
if st.button("Delete", key=f"delete_{i}"):
|
139 |
+
st.session_state["annotations"].pop(i)
|
140 |
+
st.rerun()
|
141 |
+
def save_annotations(image, uploaded_file):
|
142 |
+
"""Handle saving and downloading annotations"""
|
143 |
+
st.sidebar.subheader("πΎ Save & Export")
|
144 |
+
|
145 |
+
if st.sidebar.button("Save and Download"):
|
146 |
+
# Create annotated image
|
147 |
+
annotated_image = image.copy()
|
148 |
+
draw = ImageDraw.Draw(annotated_image)
|
149 |
+
|
150 |
+
# Draw annotations
|
151 |
+
for annotation in st.session_state["annotations"]:
|
152 |
+
obj = annotation["object_data"]
|
153 |
+
if obj["type"] == "rect":
|
154 |
+
draw.rectangle(
|
155 |
+
[obj["left"], obj["top"],
|
156 |
+
obj["left"] + obj["width"],
|
157 |
+
obj["top"] + obj["height"]],
|
158 |
+
outline=obj["stroke"],
|
159 |
+
width=int(obj["strokeWidth"])
|
160 |
+
)
|
161 |
+
# Add text label
|
162 |
+
draw.text(
|
163 |
+
(obj["left"], obj["top"] - 15),
|
164 |
+
f"{annotation['category']}: {annotation['text']}",
|
165 |
+
fill=obj["stroke"]
|
166 |
+
)
|
167 |
+
|
168 |
+
# Save files
|
169 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
170 |
+
|
171 |
+
# Save annotated image
|
172 |
+
img_buffer = io.BytesIO()
|
173 |
+
annotated_image.save(img_buffer, format="JPEG", quality=95)
|
174 |
+
img_buffer.seek(0)
|
175 |
+
|
176 |
+
# Save annotations JSON
|
177 |
+
annotations_data = {
|
178 |
+
"image_id": uploaded_file.name,
|
179 |
+
"timestamp": timestamp,
|
180 |
+
"annotations": [
|
181 |
+
{
|
182 |
+
"category": ann["category"],
|
183 |
+
"text": ann["text"],
|
184 |
+
"timestamp": ann["timestamp"],
|
185 |
+
"object_data": ann["object_data"]
|
186 |
+
}
|
187 |
+
for ann in st.session_state["annotations"]
|
188 |
+
]
|
189 |
+
}
|
190 |
+
|
191 |
+
# Convert JSON to string first, then to bytes
|
192 |
+
json_str = json.dumps(annotations_data, indent=2)
|
193 |
+
json_bytes = json_str.encode('utf-8')
|
194 |
+
json_buffer = io.BytesIO(json_bytes)
|
195 |
+
|
196 |
+
# Download buttons
|
197 |
+
col1, col2 = st.sidebar.columns(2)
|
198 |
+
with col1:
|
199 |
+
st.download_button(
|
200 |
+
"π· Download Image",
|
201 |
+
data=img_buffer,
|
202 |
+
file_name=f"annotated_{timestamp}.jpg",
|
203 |
+
mime="image/jpeg"
|
204 |
+
)
|
205 |
+
with col2:
|
206 |
+
st.download_button(
|
207 |
+
"π Download JSON",
|
208 |
+
data=json_buffer,
|
209 |
+
file_name=f"annotations_{timestamp}.json",
|
210 |
+
mime="application/json"
|
211 |
+
)
|
212 |
+
|
213 |
+
st.sidebar.success("β
Files saved successfully!")
|
214 |
+
def main():
|
215 |
+
"""Main application logic"""
|
216 |
+
initialize_session_state()
|
217 |
+
|
218 |
+
if not authenticate_user():
|
219 |
+
return
|
220 |
+
|
221 |
+
# File upload
|
222 |
+
uploaded_file = st.file_uploader(
|
223 |
+
"π€ Upload Prescription Image",
|
224 |
+
type=["jpg", "jpeg", "png"],
|
225 |
+
help="Upload a clear image of the prescription to annotate"
|
226 |
+
)
|
227 |
+
|
228 |
+
if uploaded_file:
|
229 |
+
# Load and display image
|
230 |
+
image = Image.open(uploaded_file).convert("RGB")
|
231 |
+
|
232 |
+
# Create two columns for layout
|
233 |
+
col1, col2 = st.columns([2, 1])
|
234 |
+
|
235 |
+
with col1:
|
236 |
+
# Get annotation settings
|
237 |
+
stroke_color, stroke_width, category = create_sidebar_controls()
|
238 |
+
|
239 |
+
# Handle canvas drawing
|
240 |
+
handle_canvas_drawing(image, stroke_color, stroke_width, category)
|
241 |
+
|
242 |
+
with col2:
|
243 |
+
# Undo/Redo buttons
|
244 |
+
col_undo, col_redo, col_clear = st.columns(3)
|
245 |
+
with col_undo:
|
246 |
+
if st.button("β©οΈ Undo") and st.session_state["annotations"]:
|
247 |
+
last_annotation = st.session_state["annotations"].pop()
|
248 |
+
st.session_state["annotation_history"].append(last_annotation)
|
249 |
+
|
250 |
+
with col_redo:
|
251 |
+
if st.button("βͺοΈ Redo") and st.session_state["annotation_history"]:
|
252 |
+
st.session_state["annotations"].append(
|
253 |
+
st.session_state["annotation_history"].pop()
|
254 |
+
)
|
255 |
+
|
256 |
+
with col_clear:
|
257 |
+
if st.button("ποΈ Clear All"):
|
258 |
+
st.session_state["annotations"] = []
|
259 |
+
st.session_state["annotation_history"] = []
|
260 |
+
|
261 |
+
# Display annotation list
|
262 |
+
display_annotation_list()
|
263 |
+
|
264 |
+
# Save and download options
|
265 |
+
save_annotations(image, uploaded_file)
|
266 |
+
|
267 |
+
# Footer
|
268 |
+
st.markdown("---")
|
269 |
+
st.markdown(
|
270 |
+
"""
|
271 |
+
<div style='text-align: center'>
|
272 |
+
<p><strong>Alyse AI Prescription Annotation Tool v2.0</strong></p>
|
273 |
+
<p>Created by: Jad Tounsi El Azzoiani and Amine Tahiri</p>
|
274 |
+
<p>Last Updated: November 2024</p>
|
275 |
+
</div>
|
276 |
+
""",
|
277 |
+
unsafe_allow_html=True
|
278 |
+
)
|
279 |
+
|
280 |
+
if __name__ == "__main__":
|
281 |
+
st.set_page_config(
|
282 |
+
page_title="Alyse AI Prescription Annotator",
|
283 |
+
page_icon="π₯",
|
284 |
+
layout="wide"
|
285 |
+
)
|
286 |
+
main()
|