Upload 7 files
Browse files- LICENSE +21 -0
- README.md +4 -12
- app.py +111 -0
- data/0497c5.png +0 -0
- data/857e7d.png +0 -0
- img/map.png +0 -0
- requirements.txt +7 -0
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2024 Weichen Huang
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,13 +1,5 @@
|
|
| 1 |
-
|
| 2 |
-
title: GeoIA
|
| 3 |
-
emoji: 📈
|
| 4 |
-
colorFrom: green
|
| 5 |
-
colorTo: green
|
| 6 |
-
sdk: streamlit
|
| 7 |
-
sdk_version: 1.30.0
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
license: mit
|
| 11 |
-
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
# Streamlit - Drawable Canvas - Demo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
Streamlit Drawable Canvas demo on Streamlit Sharing
|
| 4 |
+
|
| 5 |
+
[](https://share.streamlit.io/andfanilo/streamlit-drawable-canvas-demo/master/app.py)
|
app.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from streamlit_drawable_canvas import st_canvas
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import numpy as np
|
| 5 |
+
import cv2
|
| 6 |
+
import hashlib
|
| 7 |
+
import time
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
# Function to convert the canvas drawing to a binary mask
|
| 11 |
+
def canvas_to_mask(canvas_result, img_shape):
|
| 12 |
+
if canvas_result is not None and canvas_result.image_data is not None:
|
| 13 |
+
canvas_image_data = np.array(canvas_result.image_data)
|
| 14 |
+
mask = cv2.cvtColor(canvas_image_data, cv2.COLOR_RGBA2GRAY)
|
| 15 |
+
mask = cv2.threshold(mask, 1, 255, cv2.THRESH_BINARY)[1]
|
| 16 |
+
mask = cv2.resize(mask, (img_shape[1], img_shape[0]))
|
| 17 |
+
return mask
|
| 18 |
+
else:
|
| 19 |
+
return None
|
| 20 |
+
|
| 21 |
+
# Function to fill enclosed areas in the binary mask
|
| 22 |
+
def fill_enclosed_areas(mask):
|
| 23 |
+
filled_mask = mask.copy()
|
| 24 |
+
h, w = filled_mask.shape[:2]
|
| 25 |
+
flood_fill_mask = np.zeros((h + 2, w + 2), np.uint8)
|
| 26 |
+
cv2.floodFill(filled_mask, flood_fill_mask, (0, 0), 255)
|
| 27 |
+
filled_mask_inv = cv2.bitwise_not(filled_mask)
|
| 28 |
+
filled_foreground = mask | filled_mask_inv
|
| 29 |
+
return filled_foreground
|
| 30 |
+
|
| 31 |
+
def generate_short_hash():
|
| 32 |
+
current_time = str(time.time())
|
| 33 |
+
sha256_hash = hashlib.sha256(current_time.encode()).hexdigest()
|
| 34 |
+
short_hash = sha256_hash[:6]
|
| 35 |
+
|
| 36 |
+
return short_hash
|
| 37 |
+
|
| 38 |
+
# Function to calculate the mean mask from all masks in the data folder
|
| 39 |
+
def calculate_mean_mask(mask_folder):
|
| 40 |
+
mask_list = []
|
| 41 |
+
for filename in os.listdir(mask_folder):
|
| 42 |
+
if filename.endswith('.png'):
|
| 43 |
+
mask_path = os.path.join(mask_folder, filename)
|
| 44 |
+
mask_image = Image.open(mask_path).convert('L') # convert to grayscale
|
| 45 |
+
mask_array = np.array(mask_image)
|
| 46 |
+
mask_list.append(mask_array)
|
| 47 |
+
if mask_list:
|
| 48 |
+
# Stack mask arrays and calculate the mean along the stack
|
| 49 |
+
mean_mask = np.mean(np.stack(mask_list), axis=0).astype(np.uint8)
|
| 50 |
+
return mean_mask
|
| 51 |
+
else:
|
| 52 |
+
return None
|
| 53 |
+
|
| 54 |
+
# Function to overlay the mean mask onto the base image
|
| 55 |
+
def overlay_mask(base_image_path, mean_mask):
|
| 56 |
+
base_image = Image.open(base_image_path).convert('RGBA')
|
| 57 |
+
mean_mask_image = Image.fromarray(mean_mask)
|
| 58 |
+
mean_mask_image = mean_mask_image.resize(base_image.size, resample=Image.BILINEAR)
|
| 59 |
+
mask_rgba = Image.merge('RGBA', (mean_mask_image, mean_mask_image, mean_mask_image, mean_mask_image))
|
| 60 |
+
final_image = Image.composite(mask_rgba, base_image, mean_mask_image)
|
| 61 |
+
return final_image
|
| 62 |
+
|
| 63 |
+
st.title("IB Geo IA Survey")
|
| 64 |
+
|
| 65 |
+
# Upload an image
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
image = Image.open("img/map.png").convert("RGB")
|
| 69 |
+
img_array = np.array(image)
|
| 70 |
+
width = st.slider('Stroke width', 0, 20, 5)
|
| 71 |
+
|
| 72 |
+
# Create a canvas for drawing
|
| 73 |
+
st.subheader("Highlight the central business district area:")
|
| 74 |
+
canvas_result = st_canvas(
|
| 75 |
+
fill_color="rgba(255, 165, 0, 0.7)", # Use an orange, semi-transparent fill
|
| 76 |
+
stroke_width=width,
|
| 77 |
+
stroke_color="rgba(255, 165, 0, 0.7)",
|
| 78 |
+
background_image=Image.open("img/map.png"),
|
| 79 |
+
update_streamlit=True,
|
| 80 |
+
height=img_array.shape[0],
|
| 81 |
+
width=img_array.shape[1],
|
| 82 |
+
drawing_mode="freedraw",
|
| 83 |
+
key="canvas",
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
if st.button("Save"):
|
| 87 |
+
|
| 88 |
+
mask = canvas_to_mask(canvas_result, img_array.shape)
|
| 89 |
+
if mask is not None:
|
| 90 |
+
mask = fill_enclosed_areas(mask)
|
| 91 |
+
cv2.imwrite(f"data/{generate_short_hash()}.png", mask)
|
| 92 |
+
else:
|
| 93 |
+
st.warning("Please draw on the image.")
|
| 94 |
+
|
| 95 |
+
if st.button("Aggregate data"):
|
| 96 |
+
|
| 97 |
+
mean_mask = calculate_mean_mask("data")
|
| 98 |
+
|
| 99 |
+
if mean_mask is not None:
|
| 100 |
+
final_image = overlay_mask("img/map.png", mean_mask)
|
| 101 |
+
|
| 102 |
+
st.image(final_image, caption='Where most people think the CBD is', use_column_width=True)
|
| 103 |
+
|
| 104 |
+
st.download_button(
|
| 105 |
+
label="Download Image",
|
| 106 |
+
data=final_image.tobytes(),
|
| 107 |
+
file_name="final_overlay.png",
|
| 108 |
+
mime="image/png"
|
| 109 |
+
)
|
| 110 |
+
else:
|
| 111 |
+
st.warning("No saved data found in the data folder.")
|
data/0497c5.png
ADDED
|
data/857e7d.png
ADDED
|
img/map.png
ADDED
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=0.88
|
| 2 |
+
streamlit-drawable-canvas>=0.8
|
| 3 |
+
svgpathtools
|
| 4 |
+
svgwrite
|
| 5 |
+
numpy
|
| 6 |
+
pillow
|
| 7 |
+
opencv-python
|