Spaces:
Sleeping
Sleeping
Update src/ui_components_original.py
Browse files- src/ui_components_original.py +156 -10
src/ui_components_original.py
CHANGED
|
@@ -11,6 +11,7 @@ import html
|
|
| 11 |
from typing import Optional, Dict, Any
|
| 12 |
import numpy as np
|
| 13 |
import cv2
|
|
|
|
| 14 |
|
| 15 |
# ---- Safe imports for local vs package execution ----
|
| 16 |
try:
|
|
@@ -719,6 +720,7 @@ button.gr-button:hover, button.gr-button-primary:hover {
|
|
| 719 |
with gr.Row():
|
| 720 |
accept_segmentation_btn = gr.Button("β
Accept & Generate Full Report", variant="primary")
|
| 721 |
manual_edit_btn = gr.Button("βοΈ Manual Edit", variant="secondary")
|
|
|
|
| 722 |
|
| 723 |
# Manual editing section (initially hidden)
|
| 724 |
with gr.Group(visible=False) as manual_edit_group:
|
|
@@ -743,7 +745,9 @@ button.gr-button:hover, button.gr-button-primary:hover {
|
|
| 743 |
interactive=True
|
| 744 |
)
|
| 745 |
|
| 746 |
-
|
|
|
|
|
|
|
| 747 |
|
| 748 |
analysis_output = gr.HTML("")
|
| 749 |
|
|
@@ -954,25 +958,35 @@ button.gr-button:hover, button.gr-button-primary:hover {
|
|
| 954 |
return {
|
| 955 |
manual_edit_group: gr.update(visible=True),
|
| 956 |
manual_mask_input: img_path, # Load the original image for manual editing
|
| 957 |
-
analysis_output: "<div class='status-warning'>β οΈ Use the drawing tool to manually mark the wound area, then click
|
| 958 |
}
|
| 959 |
|
| 960 |
def process_manual_mask(mask_data):
|
| 961 |
"""Process the manual mask from ImageMask component"""
|
| 962 |
if not mask_data:
|
| 963 |
-
return
|
| 964 |
|
| 965 |
try:
|
| 966 |
# Extract the mask from the ImageMask component
|
| 967 |
# The mask_data contains both the background image and the drawn mask
|
| 968 |
-
if isinstance(mask_data, dict)
|
| 969 |
-
#
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 973 |
|
| 974 |
# Save the mask temporarily
|
| 975 |
-
import tempfile
|
| 976 |
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
|
| 977 |
cv2.imwrite(tmp.name, mask)
|
| 978 |
manual_mask_path = tmp.name
|
|
@@ -985,6 +999,87 @@ button.gr-button:hover, button.gr-button-primary:hover {
|
|
| 985 |
logging.error(f"Manual mask processing error: {e}")
|
| 986 |
return None
|
| 987 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 988 |
def run_full_analysis_with_manual_mask(
|
| 989 |
mode, existing_label, np_name, np_age, np_gender,
|
| 990 |
w_loc, w_dur, pain, moist, infect, diabetic,
|
|
@@ -1023,6 +1118,42 @@ button.gr-button:hover, button.gr-button-primary:hover {
|
|
| 1023 |
analysis_output: f"<div class='status-error'>β Analysis failed: {html.escape(str(e))}</div>"
|
| 1024 |
}
|
| 1025 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1026 |
def run_full_analysis_accept_segmentation(
|
| 1027 |
mode, existing_label, np_name, np_age, np_gender,
|
| 1028 |
w_loc, w_dur, pain, moist, infect, diabetic,
|
|
@@ -1156,6 +1287,13 @@ button.gr-button:hover, button.gr-button-primary:hover {
|
|
| 1156 |
[analysis_output, segmentation_preview_group, manual_edit_group]
|
| 1157 |
)
|
| 1158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1159 |
# Show manual edit interface
|
| 1160 |
manual_edit_btn.click(
|
| 1161 |
show_manual_edit_interface,
|
|
@@ -1172,6 +1310,13 @@ button.gr-button:hover, button.gr-button-primary:hover {
|
|
| 1172 |
[analysis_output, segmentation_preview_group, manual_edit_group]
|
| 1173 |
)
|
| 1174 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1175 |
history_btn.click(load_patient_history, [], [patient_history_output])
|
| 1176 |
search_patient_btn.click(search_patient_by_name, [search_patient_name], [specific_patient_output])
|
| 1177 |
view_details_btn.click(view_patient_details, [view_details_dd], [view_details_output])
|
|
@@ -1498,4 +1643,5 @@ button.gr-button:hover, button.gr-button-primary:hover {
|
|
| 1498 |
'risk_score': 0,
|
| 1499 |
'risk_level': 'Unknown',
|
| 1500 |
'risk_factors': ['Unable to assess risk due to data processing error']
|
| 1501 |
-
}
|
|
|
|
|
|
| 11 |
from typing import Optional, Dict, Any
|
| 12 |
import numpy as np
|
| 13 |
import cv2
|
| 14 |
+
import tempfile
|
| 15 |
|
| 16 |
# ---- Safe imports for local vs package execution ----
|
| 17 |
try:
|
|
|
|
| 720 |
with gr.Row():
|
| 721 |
accept_segmentation_btn = gr.Button("β
Accept & Generate Full Report", variant="primary")
|
| 722 |
manual_edit_btn = gr.Button("βοΈ Manual Edit", variant="secondary")
|
| 723 |
+
segmentation_only_btn = gr.Button("π― Get Segmentation Only", variant="secondary")
|
| 724 |
|
| 725 |
# Manual editing section (initially hidden)
|
| 726 |
with gr.Group(visible=False) as manual_edit_group:
|
|
|
|
| 745 |
interactive=True
|
| 746 |
)
|
| 747 |
|
| 748 |
+
with gr.Row():
|
| 749 |
+
process_manual_btn = gr.Button("π¬ Generate Report with Manual Mask", variant="primary")
|
| 750 |
+
manual_segmentation_only_btn = gr.Button("π― Get Manual Segmentation Only", variant="secondary")
|
| 751 |
|
| 752 |
analysis_output = gr.HTML("")
|
| 753 |
|
|
|
|
| 958 |
return {
|
| 959 |
manual_edit_group: gr.update(visible=True),
|
| 960 |
manual_mask_input: img_path, # Load the original image for manual editing
|
| 961 |
+
analysis_output: "<div class='status-warning'>β οΈ Use the drawing tool to manually mark the wound area, then click your desired action.</div>"
|
| 962 |
}
|
| 963 |
|
| 964 |
def process_manual_mask(mask_data):
|
| 965 |
"""Process the manual mask from ImageMask component"""
|
| 966 |
if not mask_data:
|
| 967 |
+
return None
|
| 968 |
|
| 969 |
try:
|
| 970 |
# Extract the mask from the ImageMask component
|
| 971 |
# The mask_data contains both the background image and the drawn mask
|
| 972 |
+
if isinstance(mask_data, dict):
|
| 973 |
+
# Check if composite exists (newer format)
|
| 974 |
+
if "composite" in mask_data:
|
| 975 |
+
composite_img = mask_data["composite"]
|
| 976 |
+
# Convert to grayscale and extract the drawn areas
|
| 977 |
+
gray = cv2.cvtColor(composite_img, cv2.COLOR_RGB2GRAY)
|
| 978 |
+
# Create mask where drawn areas are white (255) and background is black (0)
|
| 979 |
+
_, mask = cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY)
|
| 980 |
+
# Check if layers exist (older format)
|
| 981 |
+
elif "layers" in mask_data and len(mask_data["layers"]) > 0:
|
| 982 |
+
# Get the alpha channel from the first layer (the drawn mask)
|
| 983 |
+
alpha_channel = mask_data["layers"][0][:, :, 3]
|
| 984 |
+
# Convert to binary mask - drawn areas have alpha > 0
|
| 985 |
+
mask = np.where(alpha_channel > 0, 255, 0).astype(np.uint8)
|
| 986 |
+
else:
|
| 987 |
+
return None
|
| 988 |
|
| 989 |
# Save the mask temporarily
|
|
|
|
| 990 |
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
|
| 991 |
cv2.imwrite(tmp.name, mask)
|
| 992 |
manual_mask_path = tmp.name
|
|
|
|
| 999 |
logging.error(f"Manual mask processing error: {e}")
|
| 1000 |
return None
|
| 1001 |
|
| 1002 |
+
def get_segmentation_only(img_path, manual_mask_path=None):
|
| 1003 |
+
"""Get only the segmentation mask without full analysis"""
|
| 1004 |
+
try:
|
| 1005 |
+
if not img_path:
|
| 1006 |
+
return "<div class='status-error'>β No image provided.</div>"
|
| 1007 |
+
|
| 1008 |
+
# Run visual analysis to get segmentation
|
| 1009 |
+
image_pil = Image.open(img_path)
|
| 1010 |
+
|
| 1011 |
+
if manual_mask_path:
|
| 1012 |
+
# Use manual mask
|
| 1013 |
+
visual_results = self.wound_analyzer.analyze_wound(
|
| 1014 |
+
img_path, {}, seg_adjust=0.0, manual_mask_path=manual_mask_path
|
| 1015 |
+
)
|
| 1016 |
+
mask_type = "Manual"
|
| 1017 |
+
else:
|
| 1018 |
+
# Use automatic segmentation
|
| 1019 |
+
visual_results = self.wound_analyzer.perform_visual_analysis(image_pil)
|
| 1020 |
+
mask_type = "Automatic"
|
| 1021 |
+
|
| 1022 |
+
if not visual_results:
|
| 1023 |
+
return "<div class='status-error'>β Failed to generate segmentation.</div>"
|
| 1024 |
+
|
| 1025 |
+
# Get the segmentation mask path
|
| 1026 |
+
roi_mask_path = visual_results.get("roi_mask_path")
|
| 1027 |
+
seg_path = visual_results.get("segmentation_image_path")
|
| 1028 |
+
|
| 1029 |
+
if not roi_mask_path or not os.path.exists(roi_mask_path):
|
| 1030 |
+
return "<div class='status-error'>β Segmentation mask not found.</div>"
|
| 1031 |
+
|
| 1032 |
+
# Convert mask to base64 for display
|
| 1033 |
+
mask_b64 = self.image_to_base64(roi_mask_path)
|
| 1034 |
+
seg_b64 = self.image_to_base64(seg_path) if seg_path and os.path.exists(seg_path) else None
|
| 1035 |
+
|
| 1036 |
+
html_output = f"""
|
| 1037 |
+
<div style="max-width: 800px; margin: 0 auto; background: white; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.1); overflow: hidden;">
|
| 1038 |
+
<div style="background: linear-gradient(135deg, #3182ce 0%, #2c5aa0 100%); color: white; padding: 30px; text-align: center;">
|
| 1039 |
+
<h1 style="margin: 0; font-size: 28px; font-weight: 700;">π― {mask_type} Wound Segmentation</h1>
|
| 1040 |
+
<p style="margin: 10px 0 0 0; opacity: 0.9; font-size: 16px;">Binary mask showing wound boundaries</p>
|
| 1041 |
+
</div>
|
| 1042 |
+
|
| 1043 |
+
<div style="padding: 30px;">
|
| 1044 |
+
<div class="status-success" style="margin-bottom: 20px;">
|
| 1045 |
+
<strong>β
Segmentation Status:</strong> {mask_type} segmentation completed successfully
|
| 1046 |
+
</div>
|
| 1047 |
+
|
| 1048 |
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0;">
|
| 1049 |
+
<div style="background: #f8f9fa; padding: 20px; border-radius: 12px; text-align: center;">
|
| 1050 |
+
<h3 style="color: #2d3748; margin-top: 0;">Binary Mask</h3>
|
| 1051 |
+
{f'<img src="{mask_b64}" style="width: 100%; border-radius: 8px;" alt="Segmentation Mask">' if mask_b64 else '<p>Mask not available</p>'}
|
| 1052 |
+
<p style="color: #4a5568; margin: 10px 0 0 0; font-size: 14px;">White = Wound, Black = Background</p>
|
| 1053 |
+
</div>
|
| 1054 |
+
|
| 1055 |
+
<div style="background: #f8f9fa; padding: 20px; border-radius: 12px; text-align: center;">
|
| 1056 |
+
<h3 style="color: #2d3748; margin-top: 0;">Overlay Visualization</h3>
|
| 1057 |
+
{f'<img src="{seg_b64}" style="width: 100%; border-radius: 8px;" alt="Segmentation Overlay">' if seg_b64 else '<p>Overlay not available</p>'}
|
| 1058 |
+
<p style="color: #4a5568; margin: 10px 0 0 0; font-size: 14px;">Red overlay shows detected wound area</p>
|
| 1059 |
+
</div>
|
| 1060 |
+
</div>
|
| 1061 |
+
|
| 1062 |
+
<div style="background: #fff5f5; border: 2px solid #feb2b2; padding: 20px; border-radius: 12px; margin: 20px 0;">
|
| 1063 |
+
<h3 style="color: #c53030; margin-top: 0;">π₯ Download Instructions</h3>
|
| 1064 |
+
<p style="color: #742a2a; margin: 0;">Right-click on the binary mask image above and select "Save image as..." to download the segmentation mask for your use.</p>
|
| 1065 |
+
</div>
|
| 1066 |
+
</div>
|
| 1067 |
+
</div>
|
| 1068 |
+
"""
|
| 1069 |
+
|
| 1070 |
+
# Clean up temporary file if it exists
|
| 1071 |
+
if manual_mask_path and os.path.exists(manual_mask_path):
|
| 1072 |
+
try:
|
| 1073 |
+
os.unlink(manual_mask_path)
|
| 1074 |
+
except:
|
| 1075 |
+
pass
|
| 1076 |
+
|
| 1077 |
+
return html_output
|
| 1078 |
+
|
| 1079 |
+
except Exception as e:
|
| 1080 |
+
logging.error(f"Segmentation only error: {e}")
|
| 1081 |
+
return f"<div class='status-error'>β Error: {html.escape(str(e))}</div>"
|
| 1082 |
+
|
| 1083 |
def run_full_analysis_with_manual_mask(
|
| 1084 |
mode, existing_label, np_name, np_age, np_gender,
|
| 1085 |
w_loc, w_dur, pain, moist, infect, diabetic,
|
|
|
|
| 1118 |
analysis_output: f"<div class='status-error'>β Analysis failed: {html.escape(str(e))}</div>"
|
| 1119 |
}
|
| 1120 |
|
| 1121 |
+
def get_manual_segmentation_only(mask_data, img_path):
|
| 1122 |
+
"""Get only the manual segmentation mask"""
|
| 1123 |
+
try:
|
| 1124 |
+
manual_mask_path = process_manual_mask(mask_data)
|
| 1125 |
+
if not manual_mask_path:
|
| 1126 |
+
return "<div class='status-error'>β Failed to process manual mask.</div>"
|
| 1127 |
+
|
| 1128 |
+
result = get_segmentation_only(img_path, manual_mask_path)
|
| 1129 |
+
return {
|
| 1130 |
+
analysis_output: result,
|
| 1131 |
+
segmentation_preview_group: gr.update(visible=False),
|
| 1132 |
+
manual_edit_group: gr.update(visible=False)
|
| 1133 |
+
}
|
| 1134 |
+
|
| 1135 |
+
except Exception as e:
|
| 1136 |
+
logging.error(f"Manual segmentation only error: {e}")
|
| 1137 |
+
return {
|
| 1138 |
+
analysis_output: f"<div class='status-error'>β Error: {html.escape(str(e))}</div>"
|
| 1139 |
+
}
|
| 1140 |
+
|
| 1141 |
+
def get_auto_segmentation_only(img_path):
|
| 1142 |
+
"""Get only the automatic segmentation mask"""
|
| 1143 |
+
try:
|
| 1144 |
+
result = get_segmentation_only(img_path, None)
|
| 1145 |
+
return {
|
| 1146 |
+
analysis_output: result,
|
| 1147 |
+
segmentation_preview_group: gr.update(visible=False),
|
| 1148 |
+
manual_edit_group: gr.update(visible=False)
|
| 1149 |
+
}
|
| 1150 |
+
|
| 1151 |
+
except Exception as e:
|
| 1152 |
+
logging.error(f"Auto segmentation only error: {e}")
|
| 1153 |
+
return {
|
| 1154 |
+
analysis_output: f"<div class='status-error'>β Error: {html.escape(str(e))}</div>"
|
| 1155 |
+
}
|
| 1156 |
+
|
| 1157 |
def run_full_analysis_accept_segmentation(
|
| 1158 |
mode, existing_label, np_name, np_age, np_gender,
|
| 1159 |
w_loc, w_dur, pain, moist, infect, diabetic,
|
|
|
|
| 1287 |
[analysis_output, segmentation_preview_group, manual_edit_group]
|
| 1288 |
)
|
| 1289 |
|
| 1290 |
+
# Get segmentation only (automatic)
|
| 1291 |
+
segmentation_only_btn.click(
|
| 1292 |
+
get_auto_segmentation_only,
|
| 1293 |
+
[wound_image],
|
| 1294 |
+
[analysis_output, segmentation_preview_group, manual_edit_group]
|
| 1295 |
+
)
|
| 1296 |
+
|
| 1297 |
# Show manual edit interface
|
| 1298 |
manual_edit_btn.click(
|
| 1299 |
show_manual_edit_interface,
|
|
|
|
| 1310 |
[analysis_output, segmentation_preview_group, manual_edit_group]
|
| 1311 |
)
|
| 1312 |
|
| 1313 |
+
# Get manual segmentation only
|
| 1314 |
+
manual_segmentation_only_btn.click(
|
| 1315 |
+
get_manual_segmentation_only,
|
| 1316 |
+
[manual_mask_input, wound_image],
|
| 1317 |
+
[analysis_output, segmentation_preview_group, manual_edit_group]
|
| 1318 |
+
)
|
| 1319 |
+
|
| 1320 |
history_btn.click(load_patient_history, [], [patient_history_output])
|
| 1321 |
search_patient_btn.click(search_patient_by_name, [search_patient_name], [specific_patient_output])
|
| 1322 |
view_details_btn.click(view_patient_details, [view_details_dd], [view_details_output])
|
|
|
|
| 1643 |
'risk_score': 0,
|
| 1644 |
'risk_level': 'Unknown',
|
| 1645 |
'risk_factors': ['Unable to assess risk due to data processing error']
|
| 1646 |
+
}
|
| 1647 |
+
|