Spaces:
Sleeping
Sleeping
samcoding5854
commited on
Commit
β’
3b6c254
1
Parent(s):
fd45eb4
Completed creating visuals
Browse files- .gitignore +3 -1
- App/__pycache__/AboutMe.cpython-310.pyc +0 -0
- App/__pycache__/bgImages.cpython-310.pyc +0 -0
- App/__pycache__/createVisual.cpython-310.pyc +0 -0
- App/createVisual.py +0 -12
- {App β Pages}/AboutMe.py +0 -0
- {App β Pages}/bgImages.py +0 -0
- Pages/createVideo.py +53 -0
- Pages/createVisual.py +41 -0
- Pages/imageBB.py +168 -0
- Pages/streamlit_img_label/__init__.py +177 -0
- Pages/streamlit_img_label/annotation.py +65 -0
- Pages/streamlit_img_label/frontend/.gitignore +24 -0
- Pages/streamlit_img_label/frontend/.prettierrc +6 -0
- Pages/streamlit_img_label/frontend/package.json +46 -0
- Pages/streamlit_img_label/frontend/public/bootstrap.min.css +0 -0
- Pages/streamlit_img_label/frontend/public/index.html +25 -0
- Pages/streamlit_img_label/frontend/src/StreamlitImgLabel.module.css +17 -0
- Pages/streamlit_img_label/frontend/src/StreamlitImgLabel.tsx +249 -0
- Pages/streamlit_img_label/frontend/src/index.tsx +10 -0
- Pages/streamlit_img_label/frontend/src/react-app-env.d.ts +1 -0
- Pages/streamlit_img_label/frontend/tsconfig.json +19 -0
- Pages/streamlit_img_label/manage.py +186 -0
- {App β Pages}/utils.py +0 -0
- app.py +7 -4
- output/overlay_image.png +0 -0
- requirements.txt +7 -1
- saved_masks/inverted_mask_0.png +0 -0
- setup.sh +9 -0
- uploaded_images/download.png +0 -0
.gitignore
CHANGED
@@ -1 +1,3 @@
|
|
1 |
-
.streamlit
|
|
|
|
|
|
1 |
+
.streamlit
|
2 |
+
**/__pycache__
|
3 |
+
/weights
|
App/__pycache__/AboutMe.cpython-310.pyc
DELETED
Binary file (3.4 kB)
|
|
App/__pycache__/bgImages.cpython-310.pyc
DELETED
Binary file (1.98 kB)
|
|
App/__pycache__/createVisual.cpython-310.pyc
DELETED
Binary file (396 Bytes)
|
|
App/createVisual.py
DELETED
@@ -1,12 +0,0 @@
|
|
1 |
-
import streamlit as st
|
2 |
-
|
3 |
-
# Main function
|
4 |
-
def CREATEVISUALS():
|
5 |
-
st.title("Detective Game")
|
6 |
-
st.write("Hey how are you")
|
7 |
-
|
8 |
-
|
9 |
-
if __name__ == "__main__":
|
10 |
-
CREATEVISUALS()
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{App β Pages}/AboutMe.py
RENAMED
File without changes
|
{App β Pages}/bgImages.py
RENAMED
File without changes
|
Pages/createVideo.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image
|
2 |
+
import streamlit as st
|
3 |
+
import os
|
4 |
+
|
5 |
+
# Main function
|
6 |
+
def CREATEGIF():
|
7 |
+
st.title("Create photoshoot visual")
|
8 |
+
|
9 |
+
# Set the directory where the uploaded images will be saved
|
10 |
+
UPLOAD_DIR = 'uploaded_images'
|
11 |
+
|
12 |
+
# Create the directory if it doesn't exist
|
13 |
+
if not os.path.exists(UPLOAD_DIR):
|
14 |
+
os.makedirs(UPLOAD_DIR)
|
15 |
+
|
16 |
+
# Streamlit app title
|
17 |
+
st.title("Image Upload and Save App")
|
18 |
+
|
19 |
+
# File uploader allows user to upload an image
|
20 |
+
uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])
|
21 |
+
|
22 |
+
if uploaded_file is not None:
|
23 |
+
# Open the uploaded image
|
24 |
+
image = Image.open(uploaded_file)
|
25 |
+
|
26 |
+
# Display the uploaded image
|
27 |
+
st.image(image, caption='Uploaded Image.', use_column_width=True)
|
28 |
+
|
29 |
+
# Save the uploaded image to the specified directory
|
30 |
+
image_path = os.path.join(UPLOAD_DIR, uploaded_file.name)
|
31 |
+
image.save(image_path)
|
32 |
+
|
33 |
+
st.write(f"Image saved at: {image_path}")
|
34 |
+
|
35 |
+
else:
|
36 |
+
st.write("No image uploaded yet.")
|
37 |
+
|
38 |
+
image_files = [f for f in os.listdir("bgImages") if os.path.isfile(os.path.join("bgImages", f))]
|
39 |
+
|
40 |
+
# Create a dropdown with the list of image files
|
41 |
+
selected_image = st.selectbox("Select an image file", image_files)
|
42 |
+
|
43 |
+
if selected_image:
|
44 |
+
# Display the selected image
|
45 |
+
image_path = os.path.join("bgImages", selected_image)
|
46 |
+
image = Image.open(image_path)
|
47 |
+
st.image(image, caption=f"Selected image: {selected_image}")
|
48 |
+
|
49 |
+
|
50 |
+
if __name__ == "__main__":
|
51 |
+
CREATEGIF()
|
52 |
+
|
53 |
+
|
Pages/createVisual.py
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image
|
2 |
+
import streamlit as st
|
3 |
+
import os
|
4 |
+
from Pages.imageBB import run
|
5 |
+
|
6 |
+
# Main function
|
7 |
+
def CREATEVISUALS():
|
8 |
+
st.title("Create photoshoot visual")
|
9 |
+
|
10 |
+
# Set the directory where the uploaded images will be saved
|
11 |
+
UPLOAD_DIR = 'uploaded_images'
|
12 |
+
|
13 |
+
# Create the directory if it doesn't exist
|
14 |
+
if not os.path.exists(UPLOAD_DIR):
|
15 |
+
os.makedirs(UPLOAD_DIR)
|
16 |
+
|
17 |
+
# Streamlit app title
|
18 |
+
st.header("Image Upload and Save App")
|
19 |
+
|
20 |
+
# File uploader allows user to upload an image
|
21 |
+
uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])
|
22 |
+
|
23 |
+
if uploaded_file is not None:
|
24 |
+
# Open the uploaded image
|
25 |
+
image = Image.open(uploaded_file)
|
26 |
+
|
27 |
+
# Save the uploaded image to the specified directory
|
28 |
+
image_path = os.path.join(UPLOAD_DIR, uploaded_file.name)
|
29 |
+
image.save(image_path)
|
30 |
+
|
31 |
+
st.write(f"Image Saved")
|
32 |
+
run(image_path)
|
33 |
+
|
34 |
+
|
35 |
+
else:
|
36 |
+
st.write("No image uploaded yet.")
|
37 |
+
|
38 |
+
if __name__ == "__main__":
|
39 |
+
CREATEVISUALS()
|
40 |
+
|
41 |
+
|
Pages/imageBB.py
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from segment_anything import sam_model_registry, SamAutomaticMaskGenerator, SamPredictor
|
2 |
+
import torch
|
3 |
+
import streamlit as st
|
4 |
+
from Pages.streamlit_img_label import st_img_label
|
5 |
+
from Pages.streamlit_img_label.manage import ImageManager
|
6 |
+
import os
|
7 |
+
from PIL import Image
|
8 |
+
import cv2
|
9 |
+
import numpy as np
|
10 |
+
|
11 |
+
@st.cache_data
|
12 |
+
def get_masks(rect,img_path):
|
13 |
+
|
14 |
+
CHECKPOINT_PATH = os.path.join("weights", "sam_vit_h_4b8939.pth")
|
15 |
+
DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
|
16 |
+
MODEL_TYPE = "vit_h"
|
17 |
+
sam = sam_model_registry[MODEL_TYPE](checkpoint=CHECKPOINT_PATH).to(device=DEVICE)
|
18 |
+
mask_predictor = SamPredictor(sam)
|
19 |
+
|
20 |
+
rect = np.array([
|
21 |
+
rect['left'],
|
22 |
+
rect['top'],
|
23 |
+
rect['left'] + rect['width'],
|
24 |
+
rect['top'] + rect['height']
|
25 |
+
])
|
26 |
+
image_bgr = cv2.imread(img_path)
|
27 |
+
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
|
28 |
+
|
29 |
+
mask_predictor.set_image(image_rgb)
|
30 |
+
|
31 |
+
masks, scores, logits = mask_predictor.predict(
|
32 |
+
box=rect,
|
33 |
+
multimask_output=False
|
34 |
+
)
|
35 |
+
return masks
|
36 |
+
|
37 |
+
|
38 |
+
|
39 |
+
def run(img_path):
|
40 |
+
st.set_option("deprecation.showfileUploaderEncoding", False)
|
41 |
+
|
42 |
+
im = ImageManager(img_path)
|
43 |
+
resized_img = im.resizing_img()
|
44 |
+
resized_rects = im.get_resized_rects()
|
45 |
+
|
46 |
+
if "rects" not in st.session_state:
|
47 |
+
st.session_state.rects = resized_rects
|
48 |
+
|
49 |
+
# Only display st_img_label if Save button hasn't been clicked
|
50 |
+
if not st.session_state.get("saved"):
|
51 |
+
rects = st_img_label(resized_img, box_color="red", rects=st.session_state.rects)
|
52 |
+
st.session_state.rects = rects
|
53 |
+
else:
|
54 |
+
st.image(resized_img, caption="Annotated Image",width=300, use_column_width=True)
|
55 |
+
|
56 |
+
for rect in st.session_state.rects:
|
57 |
+
# st.write(f"Rectangle: {rect}")
|
58 |
+
|
59 |
+
with st.spinner('Please wait while the product image is being extracted...'):
|
60 |
+
|
61 |
+
masks = get_masks(rect,img_path)
|
62 |
+
|
63 |
+
save_dir = "saved_masks"
|
64 |
+
if not os.path.exists(save_dir):
|
65 |
+
os.makedirs(save_dir)
|
66 |
+
|
67 |
+
for i, mask in enumerate(masks):
|
68 |
+
inverted_mask = 255 - (mask * 255).astype(np.uint8)
|
69 |
+
file_path = os.path.join(save_dir, f"inverted_mask_{i}.png")
|
70 |
+
cv2.imwrite(file_path, inverted_mask)
|
71 |
+
|
72 |
+
print(f"Inverted masks saved to directory: {save_dir}")
|
73 |
+
|
74 |
+
image_files = [f for f in os.listdir("bgImages") if os.path.isfile(os.path.join("bgImages", f))]
|
75 |
+
|
76 |
+
st.header("Template Selection")
|
77 |
+
# Create a dropdown with the list of image files
|
78 |
+
selected_image = st.selectbox("Select an image file", image_files)
|
79 |
+
|
80 |
+
if selected_image:
|
81 |
+
# Display the selected image
|
82 |
+
image_pathBG = os.path.join("bgImages", selected_image)
|
83 |
+
image = Image.open(image_pathBG)
|
84 |
+
st.image(image, width=300, caption=f"Selected image: {selected_image}")
|
85 |
+
|
86 |
+
if st.button("Create Image"):
|
87 |
+
# Read the base image and background image
|
88 |
+
image_bgr = cv2.imread(img_path)
|
89 |
+
background_bgr = cv2.imread(image_pathBG)
|
90 |
+
|
91 |
+
# Resize the background image to match the size of image_bgr
|
92 |
+
background_bgr = cv2.resize(background_bgr, (image_bgr.shape[1], image_bgr.shape[0]))
|
93 |
+
|
94 |
+
# Convert the base image to RGB format for mask prediction if it's not already in RGB
|
95 |
+
if image_bgr.shape[2] == 3: # No alpha channel, standard BGR image
|
96 |
+
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
|
97 |
+
else:
|
98 |
+
image_rgb = image_bgr[:, :, :3] # Drop alpha channel if it exists
|
99 |
+
|
100 |
+
# Assuming masks is a binary mask, convert it to uint8 format
|
101 |
+
mask = (masks[0] > 0).astype(np.uint8) * 255
|
102 |
+
|
103 |
+
# Apply a Gaussian blur to the mask to smooth the edges
|
104 |
+
mask = cv2.GaussianBlur(mask, (3,3), 0)
|
105 |
+
|
106 |
+
# Ensure the image has an alpha channel
|
107 |
+
if image_bgr.shape[2] == 3: # If no alpha channel, add one
|
108 |
+
b, g, r = cv2.split(image_bgr)
|
109 |
+
alpha_channel = mask # Use the blurred mask as the alpha channel
|
110 |
+
image_bgra = cv2.merge((b, g, r, alpha_channel))
|
111 |
+
else:
|
112 |
+
image_bgra = image_bgr
|
113 |
+
|
114 |
+
# Get the dimensions of the images
|
115 |
+
masked_height, masked_width = image_bgra.shape[:2]
|
116 |
+
background_height, background_width = background_bgr.shape[:2]
|
117 |
+
|
118 |
+
# Calculate the coordinates to place the masked image in the center of the background image
|
119 |
+
x_offset = (background_width - masked_width) // 2
|
120 |
+
y_offset = (background_height - masked_height) // 2
|
121 |
+
|
122 |
+
# Resize the masked image if it is larger than the background area
|
123 |
+
if masked_width > background_width or masked_height > background_height:
|
124 |
+
scaling_factor = min(background_width / masked_width, background_height / masked_height)
|
125 |
+
new_size = (int(masked_width * scaling_factor), int(masked_height * scaling_factor))
|
126 |
+
image_bgra = cv2.resize(image_bgra, new_size, interpolation=cv2.INTER_AREA)
|
127 |
+
masked_height, masked_width = image_bgra.shape[:2]
|
128 |
+
x_offset = (background_width - masked_width) // 2
|
129 |
+
y_offset = (background_height - masked_height) // 2
|
130 |
+
|
131 |
+
# Create a copy of the background image and convert it to BGRA
|
132 |
+
background_bgra = cv2.cvtColor(background_bgr, cv2.COLOR_BGR2BGRA)
|
133 |
+
|
134 |
+
# Overlay the masked image onto the center of the background image
|
135 |
+
overlay_image = background_bgra.copy()
|
136 |
+
|
137 |
+
# Only update the region where the segmented image will be placed
|
138 |
+
overlay = np.zeros_like(background_bgra)
|
139 |
+
overlay[y_offset:y_offset+masked_height, x_offset:x_offset+masked_width] = image_bgra
|
140 |
+
|
141 |
+
# Create the alpha mask for blending
|
142 |
+
alpha_mask = overlay[:, :, 3] / 255.0
|
143 |
+
alpha_inv = 1.0 - alpha_mask
|
144 |
+
|
145 |
+
# Modify alpha channel for smoother blending
|
146 |
+
alpha_mask = alpha_mask ** 0.5 # Applying square root for smoother blending
|
147 |
+
|
148 |
+
# Blend the images
|
149 |
+
for c in range(0, 3):
|
150 |
+
overlay_image[:, :, c] = (alpha_mask * overlay[:, :, c] + alpha_inv * overlay_image[:, :, c])
|
151 |
+
|
152 |
+
# Set the alpha channel
|
153 |
+
overlay_image[:, :, 3] = np.clip(overlay[:, :, 3] + background_bgra[:, :, 3], 0, 255)
|
154 |
+
|
155 |
+
# Save the result
|
156 |
+
output_path = 'output/overlay_image.png'
|
157 |
+
cv2.imwrite(output_path, overlay_image)
|
158 |
+
|
159 |
+
# Display the overlay image
|
160 |
+
st.image(output_path, caption="Overlay Image", use_column_width=True, width=300)
|
161 |
+
|
162 |
+
|
163 |
+
def annotate():
|
164 |
+
st.session_state.saved = True
|
165 |
+
|
166 |
+
if st.session_state.rects:
|
167 |
+
st.button(label="Save", on_click=annotate)
|
168 |
+
|
Pages/streamlit_img_label/__init__.py
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import streamlit.components.v1 as components
|
3 |
+
import numpy as np
|
4 |
+
from .manage import ImageManager, ImageDirManager
|
5 |
+
|
6 |
+
_RELEASE = True
|
7 |
+
|
8 |
+
if not _RELEASE:
|
9 |
+
_component_func = components.declare_component(
|
10 |
+
"st_img_label",
|
11 |
+
url="http://localhost:3001",
|
12 |
+
)
|
13 |
+
else:
|
14 |
+
parent_dir = os.path.dirname(os.path.abspath(__file__))
|
15 |
+
build_dir = os.path.join(parent_dir, "frontend/build")
|
16 |
+
_component_func = components.declare_component("st_img_label", path=build_dir)
|
17 |
+
|
18 |
+
|
19 |
+
def st_img_label(resized_img, box_color="blue", rects=[], key=None):
|
20 |
+
"""Create a new instance of "st_img_label".
|
21 |
+
|
22 |
+
Parameters
|
23 |
+
----------
|
24 |
+
img_file: PIL.Image
|
25 |
+
The image to be croppepd
|
26 |
+
box_color: string
|
27 |
+
The color of the cropper's bounding box. Defaults to blue.
|
28 |
+
rects: list
|
29 |
+
list of bounding boxes that already exists.
|
30 |
+
key: str or None
|
31 |
+
An optional key that uniquely identifies this component. If this is
|
32 |
+
None, and the component's arguments are changed, the component will
|
33 |
+
be re-mounted in the Streamlit frontend and lose its current state.
|
34 |
+
|
35 |
+
Returns
|
36 |
+
-------
|
37 |
+
rects: list
|
38 |
+
list of bounding boxes.
|
39 |
+
"""
|
40 |
+
# Get arguments to send to frontend
|
41 |
+
canvasWidth = resized_img.width
|
42 |
+
canvasHeight = resized_img.height
|
43 |
+
|
44 |
+
# Translates image to a list for passing to Javascript
|
45 |
+
imageData = np.array(resized_img.convert("RGBA")).flatten().tolist()
|
46 |
+
|
47 |
+
# Call through to our private component function. Arguments we pass here
|
48 |
+
# will be sent to the frontend, where they'll be available in an "args"
|
49 |
+
# dictionary.
|
50 |
+
#
|
51 |
+
# Defaults to a box whose vertices are at 20% and 80% of height and width.
|
52 |
+
# The _recommended_box function could be replaced with some kind of image
|
53 |
+
# detection algorith if it suits your needs.
|
54 |
+
component_value = _component_func(
|
55 |
+
canvasWidth=canvasWidth,
|
56 |
+
canvasHeight=canvasHeight,
|
57 |
+
rects=rects,
|
58 |
+
boxColor=box_color,
|
59 |
+
imageData=imageData,
|
60 |
+
key=key,
|
61 |
+
)
|
62 |
+
# Return a cropped image using the box from the frontend
|
63 |
+
if component_value:
|
64 |
+
return component_value["rects"]
|
65 |
+
else:
|
66 |
+
return rects
|
67 |
+
|
68 |
+
|
69 |
+
# Add some test code to play with the component while it's in development.
|
70 |
+
# During development, we can run this just as we would any other Streamlit
|
71 |
+
# app: `$ streamlit run my_component/__init__.py`
|
72 |
+
if not _RELEASE:
|
73 |
+
import streamlit as st
|
74 |
+
|
75 |
+
st.set_option("deprecation.showfileUploaderEncoding", False)
|
76 |
+
custom_labels = ["", "dog", "cat"]
|
77 |
+
|
78 |
+
img_dir = "img_dir"
|
79 |
+
|
80 |
+
idm = ImageDirManager(img_dir)
|
81 |
+
|
82 |
+
if "files" not in st.session_state:
|
83 |
+
st.session_state["files"] = idm.get_all_files()
|
84 |
+
st.session_state["annotation_files"] = idm.get_exist_annotation_files()
|
85 |
+
st.session_state["image_index"] = 0
|
86 |
+
else:
|
87 |
+
idm.set_all_files(st.session_state["files"])
|
88 |
+
idm.set_annotation_files(st.session_state["annotation_files"])
|
89 |
+
|
90 |
+
def refresh():
|
91 |
+
st.session_state["files"] = idm.get_all_files()
|
92 |
+
st.session_state["annotation_files"] = idm.get_exist_annotation_files()
|
93 |
+
st.session_state["image_index"] = 0
|
94 |
+
|
95 |
+
def next_image():
|
96 |
+
image_index = st.session_state["image_index"]
|
97 |
+
if image_index < len(st.session_state["files"]) - 1:
|
98 |
+
st.session_state["image_index"] += 1
|
99 |
+
else:
|
100 |
+
st.warning("This is the last image.")
|
101 |
+
|
102 |
+
def previous_image():
|
103 |
+
image_index = st.session_state["image_index"]
|
104 |
+
if image_index > 0:
|
105 |
+
st.session_state["image_index"] -= 1
|
106 |
+
else:
|
107 |
+
st.warning("This is the first image.")
|
108 |
+
|
109 |
+
def next_annotate_file():
|
110 |
+
image_index = st.session_state["image_index"]
|
111 |
+
next_image_index = idm.get_next_annotation_image(image_index)
|
112 |
+
if next_image_index:
|
113 |
+
st.session_state["image_index"] = idm.get_next_annotation_image(image_index)
|
114 |
+
else:
|
115 |
+
st.warning("All images are annotated.")
|
116 |
+
next_image()
|
117 |
+
|
118 |
+
def go_to_image():
|
119 |
+
file_index = st.session_state["files"].index(st.session_state["file"])
|
120 |
+
st.session_state["image_index"] = file_index
|
121 |
+
|
122 |
+
# Sidebar: show status
|
123 |
+
n_files = len(st.session_state["files"])
|
124 |
+
n_annotate_files = len(st.session_state["annotation_files"])
|
125 |
+
st.sidebar.write("Total files:", n_files)
|
126 |
+
st.sidebar.write("Total annotate files:", n_annotate_files)
|
127 |
+
st.sidebar.write("Remaining files:", n_files - n_annotate_files)
|
128 |
+
|
129 |
+
st.sidebar.selectbox(
|
130 |
+
"Files",
|
131 |
+
st.session_state["files"],
|
132 |
+
index=st.session_state["image_index"],
|
133 |
+
on_change=go_to_image,
|
134 |
+
key="file",
|
135 |
+
)
|
136 |
+
col1, col2 = st.sidebar.columns(2)
|
137 |
+
with col1:
|
138 |
+
st.button(label="Previous image", on_click=previous_image)
|
139 |
+
with col2:
|
140 |
+
st.button(label="Next image", on_click=next_image)
|
141 |
+
st.sidebar.button(label="Next need annotate", on_click=next_annotate_file)
|
142 |
+
st.sidebar.button(label="Refresh", on_click=refresh)
|
143 |
+
|
144 |
+
# Main content: annotate images
|
145 |
+
img_file_name = idm.get_image(st.session_state["image_index"])
|
146 |
+
img_path = os.path.join(img_dir, img_file_name)
|
147 |
+
im = ImageManager(img_path)
|
148 |
+
img = im.get_img()
|
149 |
+
resized_img = im.resizing_img()
|
150 |
+
resized_rects = im.get_resized_rects()
|
151 |
+
rects = st_img_label(resized_img, box_color="red", rects=resized_rects)
|
152 |
+
|
153 |
+
def annotate():
|
154 |
+
im.save_annotation()
|
155 |
+
image_annotate_file_name = img_file_name.split(".")[0] + ".xml"
|
156 |
+
if image_annotate_file_name not in st.session_state["annotation_files"]:
|
157 |
+
st.session_state["annotation_files"].append(image_annotate_file_name)
|
158 |
+
next_annotate_file()
|
159 |
+
|
160 |
+
if rects:
|
161 |
+
st.button(label="Save", on_click=annotate)
|
162 |
+
preview_imgs = im.init_annotation(rects)
|
163 |
+
|
164 |
+
for i, prev_img in enumerate(preview_imgs):
|
165 |
+
prev_img[0].thumbnail((200, 200))
|
166 |
+
col1, col2 = st.columns(2)
|
167 |
+
with col1:
|
168 |
+
col1.image(prev_img[0])
|
169 |
+
with col2:
|
170 |
+
default_index = 0
|
171 |
+
if prev_img[1]:
|
172 |
+
default_index = custom_labels.index(prev_img[1])
|
173 |
+
|
174 |
+
select_label = col2.selectbox(
|
175 |
+
"Label", custom_labels, key=f"label_{i}", index=default_index
|
176 |
+
)
|
177 |
+
im.set_annotation(i, select_label)
|
Pages/streamlit_img_label/annotation.py
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from pascal_voc_writer import Writer
|
3 |
+
from xml.etree import ElementTree as ET
|
4 |
+
|
5 |
+
"""
|
6 |
+
.. module:: streamlit_img_label
|
7 |
+
:synopsis: annotation.
|
8 |
+
.. moduleauthor:: Tianning Li <ltianningli@gmail.com>
|
9 |
+
"""
|
10 |
+
|
11 |
+
|
12 |
+
def read_xml(img_file):
|
13 |
+
"""read_xml
|
14 |
+
Read the xml annotation file and extract the bounding boxes if exists.
|
15 |
+
|
16 |
+
Args:
|
17 |
+
img_file(str): the image file.
|
18 |
+
Returns:
|
19 |
+
rects(list): the bounding boxes of the image.
|
20 |
+
"""
|
21 |
+
file_name = img_file.split(".")[0]
|
22 |
+
if not os.path.isfile(f"{file_name}.xml"):
|
23 |
+
return []
|
24 |
+
tree = ET.parse(f"{file_name}.xml")
|
25 |
+
root = tree.getroot()
|
26 |
+
|
27 |
+
rects = []
|
28 |
+
|
29 |
+
for boxes in root.iter("object"):
|
30 |
+
label = boxes.find("name").text
|
31 |
+
ymin = int(boxes.find("bndbox/ymin").text)
|
32 |
+
xmin = int(boxes.find("bndbox/xmin").text)
|
33 |
+
ymax = int(boxes.find("bndbox/ymax").text)
|
34 |
+
xmax = int(boxes.find("bndbox/xmax").text)
|
35 |
+
rects.append(
|
36 |
+
{
|
37 |
+
"left": xmin,
|
38 |
+
"top": ymin,
|
39 |
+
"width": xmax - xmin,
|
40 |
+
"height": ymax - ymin,
|
41 |
+
"label": label,
|
42 |
+
}
|
43 |
+
)
|
44 |
+
return rects
|
45 |
+
|
46 |
+
|
47 |
+
def output_xml(img_file, img, rects):
|
48 |
+
"""output_xml
|
49 |
+
Output the xml image annotation file
|
50 |
+
|
51 |
+
Args:
|
52 |
+
img_file(str): the image file.
|
53 |
+
img(PIL.Image): the image object.
|
54 |
+
rects(list): the bounding boxes of the image.
|
55 |
+
"""
|
56 |
+
file_name = img_file.split(".")[0]
|
57 |
+
writer = Writer(img_file, img.width, img.height)
|
58 |
+
for box in rects:
|
59 |
+
xmin = box["left"]
|
60 |
+
ymin = box["top"]
|
61 |
+
xmax = box["left"] + box["width"]
|
62 |
+
ymax = box["top"] + box["height"]
|
63 |
+
|
64 |
+
writer.addObject(box["label"], xmin, ymin, xmax, ymax)
|
65 |
+
writer.save(f"{file_name}.xml")
|
Pages/streamlit_img_label/frontend/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
2 |
+
|
3 |
+
# dependencies
|
4 |
+
/node_modules
|
5 |
+
/.pnp
|
6 |
+
.pnp.js
|
7 |
+
yarn.lock
|
8 |
+
|
9 |
+
# testing
|
10 |
+
/coverage
|
11 |
+
|
12 |
+
# production
|
13 |
+
/build
|
14 |
+
|
15 |
+
# misc
|
16 |
+
.DS_Store
|
17 |
+
.env.local
|
18 |
+
.env.development.local
|
19 |
+
.env.test.local
|
20 |
+
.env.production.local
|
21 |
+
|
22 |
+
npm-debug.log*
|
23 |
+
yarn-debug.log*
|
24 |
+
yarn-error.log*
|
Pages/streamlit_img_label/frontend/.prettierrc
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"endOfLine": "lf",
|
3 |
+
"semi": false,
|
4 |
+
"trailingComma": "es5",
|
5 |
+
"tabWidth": 4
|
6 |
+
}
|
Pages/streamlit_img_label/frontend/package.json
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "streamlit_img_label",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"private": true,
|
5 |
+
"dependencies": {
|
6 |
+
"@testing-library/jest-dom": "^4.2.4",
|
7 |
+
"@testing-library/react": "^9.3.2",
|
8 |
+
"@testing-library/user-event": "^7.1.2",
|
9 |
+
"@types/fabric": "^3.6.8",
|
10 |
+
"@types/hoist-non-react-statics": "^3.3.1",
|
11 |
+
"@types/jest": "^24.0.0",
|
12 |
+
"@types/node": "^12.0.0",
|
13 |
+
"@types/react": "^16.9.0",
|
14 |
+
"@types/react-dom": "^16.9.0",
|
15 |
+
"apache-arrow": "^0.17.0",
|
16 |
+
"hoist-non-react-statics": "^3.3.2",
|
17 |
+
"fabric": "^3.6.3",
|
18 |
+
"react": "^16.13.1",
|
19 |
+
"react-dom": "^16.13.1",
|
20 |
+
"react-scripts": "3.4.1",
|
21 |
+
"streamlit-component-lib": "^1.1.3",
|
22 |
+
"typescript": "~3.7.2"
|
23 |
+
},
|
24 |
+
"scripts": {
|
25 |
+
"start": "react-scripts start",
|
26 |
+
"build": "react-scripts build",
|
27 |
+
"test": "react-scripts test",
|
28 |
+
"eject": "react-scripts eject"
|
29 |
+
},
|
30 |
+
"eslintConfig": {
|
31 |
+
"extends": "react-app"
|
32 |
+
},
|
33 |
+
"browserslist": {
|
34 |
+
"production": [
|
35 |
+
">0.2%",
|
36 |
+
"not dead",
|
37 |
+
"not op_mini all"
|
38 |
+
],
|
39 |
+
"development": [
|
40 |
+
"last 1 chrome version",
|
41 |
+
"last 1 firefox version",
|
42 |
+
"last 1 safari version"
|
43 |
+
]
|
44 |
+
},
|
45 |
+
"homepage": "."
|
46 |
+
}
|
Pages/streamlit_img_label/frontend/public/bootstrap.min.css
ADDED
The diff for this file is too large to render.
See raw diff
|
|
Pages/streamlit_img_label/frontend/public/index.html
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<title>Streamlit Component</title>
|
5 |
+
<meta charset="UTF-8" />
|
6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
7 |
+
<meta name="theme-color" content="#000000" />
|
8 |
+
<meta name="description" content="Streamlit Component" />
|
9 |
+
<link rel="stylesheet" href="bootstrap.min.css" />
|
10 |
+
</head>
|
11 |
+
<body>
|
12 |
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
13 |
+
<div id="root"></div>
|
14 |
+
<!--
|
15 |
+
This HTML file is a template.
|
16 |
+
If you open it directly in the browser, you will see an empty page.
|
17 |
+
|
18 |
+
You can add webfonts, meta tags, or analytics to this file.
|
19 |
+
The build step will place the bundled scripts into the <body> tag.
|
20 |
+
|
21 |
+
To begin the development, run `npm start` or `yarn start`.
|
22 |
+
To create a production bundle, use `npm run build` or `yarn build`.
|
23 |
+
-->
|
24 |
+
</body>
|
25 |
+
</html>
|
Pages/streamlit_img_label/frontend/src/StreamlitImgLabel.module.css
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.dark{
|
2 |
+
background-color:rgb(14, 17, 23);
|
3 |
+
}
|
4 |
+
|
5 |
+
button{
|
6 |
+
padding: 0.25rem 0.75rem;
|
7 |
+
border-radius: 0.25rem;
|
8 |
+
line-height: 1.3;
|
9 |
+
border: 1px solid;
|
10 |
+
margin-right: 5px;
|
11 |
+
margin-top: 5px;
|
12 |
+
|
13 |
+
}
|
14 |
+
button.dark{
|
15 |
+
color: white;
|
16 |
+
border-color: rgba(250, 250, 250, 0.2);
|
17 |
+
}
|
Pages/streamlit_img_label/frontend/src/StreamlitImgLabel.tsx
ADDED
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect, useState } from "react"
|
2 |
+
import {
|
3 |
+
ComponentProps,
|
4 |
+
Streamlit,
|
5 |
+
withStreamlitConnection,
|
6 |
+
} from "streamlit-component-lib"
|
7 |
+
import { fabric } from "fabric"
|
8 |
+
import styles from "./StreamlitImgLabel.module.css"
|
9 |
+
|
10 |
+
interface RectProps {
|
11 |
+
top: number
|
12 |
+
left: number
|
13 |
+
width: number
|
14 |
+
height: number
|
15 |
+
label: string
|
16 |
+
}
|
17 |
+
|
18 |
+
interface PythonArgs {
|
19 |
+
canvasWidth: number
|
20 |
+
canvasHeight: number
|
21 |
+
rects: RectProps[]
|
22 |
+
boxColor: string
|
23 |
+
imageData: Uint8ClampedArray
|
24 |
+
}
|
25 |
+
|
26 |
+
const StreamlitImgLabel = (props: ComponentProps) => {
|
27 |
+
const [mode, setMode] = useState<string>("light")
|
28 |
+
const [labels, setLabels] = useState<string[]>([])
|
29 |
+
const [canvas, setCanvas] = useState(new fabric.Canvas(""))
|
30 |
+
const { canvasWidth, canvasHeight, imageData }: PythonArgs = props.args
|
31 |
+
const [newBBoxIndex, setNewBBoxIndex] = useState<number>(0)
|
32 |
+
|
33 |
+
/*
|
34 |
+
* Translate Python image data to a JavaScript Image
|
35 |
+
*/
|
36 |
+
var invisCanvas = document.createElement("canvas")
|
37 |
+
var ctx = invisCanvas.getContext("2d")
|
38 |
+
|
39 |
+
invisCanvas.width = canvasWidth
|
40 |
+
invisCanvas.height = canvasHeight
|
41 |
+
|
42 |
+
// create imageData object
|
43 |
+
let dataUri: any
|
44 |
+
if (ctx) {
|
45 |
+
var idata = ctx.createImageData(canvasWidth, canvasHeight)
|
46 |
+
|
47 |
+
// set our buffer as source
|
48 |
+
idata.data.set(imageData)
|
49 |
+
|
50 |
+
// update canvas with new data
|
51 |
+
ctx.putImageData(idata, 0, 0)
|
52 |
+
dataUri = invisCanvas.toDataURL()
|
53 |
+
} else {
|
54 |
+
dataUri = ""
|
55 |
+
}
|
56 |
+
|
57 |
+
// Initialize canvas on mount and add a rectangle
|
58 |
+
useEffect(() => {
|
59 |
+
const { rects, boxColor }: PythonArgs = props.args
|
60 |
+
const canvasTmp = new fabric.Canvas("c", {
|
61 |
+
enableRetinaScaling: false,
|
62 |
+
backgroundImage: dataUri,
|
63 |
+
uniScaleTransform: true,
|
64 |
+
})
|
65 |
+
|
66 |
+
rects.forEach((rect) => {
|
67 |
+
const { top, left, width, height } = rect
|
68 |
+
canvasTmp.add(
|
69 |
+
new fabric.Rect({
|
70 |
+
left,
|
71 |
+
top,
|
72 |
+
fill: "",
|
73 |
+
width,
|
74 |
+
height,
|
75 |
+
objectCaching: true,
|
76 |
+
stroke: boxColor,
|
77 |
+
strokeWidth: 1,
|
78 |
+
strokeUniform: true,
|
79 |
+
hasRotatingPoint: false,
|
80 |
+
})
|
81 |
+
)
|
82 |
+
})
|
83 |
+
setLabels(rects.map((rect) => rect.label))
|
84 |
+
|
85 |
+
setCanvas(canvasTmp)
|
86 |
+
Streamlit.setFrameHeight()
|
87 |
+
// eslint-disable-next-line
|
88 |
+
}, [canvasHeight, canvasWidth, dataUri])
|
89 |
+
|
90 |
+
// Create defualt bounding box
|
91 |
+
const defaultBox = () => ({
|
92 |
+
left: canvasWidth * 0.15 + newBBoxIndex * 3,
|
93 |
+
top: canvasHeight * 0.15 + newBBoxIndex * 3,
|
94 |
+
width: canvasWidth * 0.2,
|
95 |
+
height: canvasHeight * 0.2,
|
96 |
+
})
|
97 |
+
|
98 |
+
// Add new bounding box to be image
|
99 |
+
const addBoxHandler = () => {
|
100 |
+
const box = defaultBox()
|
101 |
+
setNewBBoxIndex(newBBoxIndex + 1)
|
102 |
+
canvas.add(
|
103 |
+
new fabric.Rect({
|
104 |
+
...box,
|
105 |
+
fill: "",
|
106 |
+
objectCaching: true,
|
107 |
+
stroke: props.args.boxColor,
|
108 |
+
strokeWidth: 1,
|
109 |
+
strokeUniform: true,
|
110 |
+
hasRotatingPoint: false,
|
111 |
+
})
|
112 |
+
)
|
113 |
+
sendCoordinates([...labels, ""])
|
114 |
+
}
|
115 |
+
|
116 |
+
// Remove the selected bounding box
|
117 |
+
const removeBoxHandler = () => {
|
118 |
+
const selectObject = canvas.getActiveObject()
|
119 |
+
const selectIndex = canvas.getObjects().indexOf(selectObject)
|
120 |
+
canvas.remove(selectObject)
|
121 |
+
sendCoordinates(labels.filter((label, i) => i !== selectIndex))
|
122 |
+
}
|
123 |
+
|
124 |
+
// Reset the bounding boxes
|
125 |
+
const resetHandler = () => {
|
126 |
+
clearHandler()
|
127 |
+
const { rects, boxColor }: PythonArgs = props.args
|
128 |
+
rects.forEach((rect) => {
|
129 |
+
const { top, left, width, height } = rect
|
130 |
+
canvas.add(
|
131 |
+
new fabric.Rect({
|
132 |
+
left,
|
133 |
+
top,
|
134 |
+
fill: "",
|
135 |
+
width,
|
136 |
+
height,
|
137 |
+
objectCaching: true,
|
138 |
+
stroke: boxColor,
|
139 |
+
strokeWidth: 1,
|
140 |
+
strokeUniform: true,
|
141 |
+
hasRotatingPoint: false,
|
142 |
+
})
|
143 |
+
)
|
144 |
+
})
|
145 |
+
sendCoordinates(labels)
|
146 |
+
}
|
147 |
+
|
148 |
+
// Remove all the bounding boxes
|
149 |
+
const clearHandler = () => {
|
150 |
+
setNewBBoxIndex(0)
|
151 |
+
canvas.getObjects().forEach((rect) => canvas.remove(rect))
|
152 |
+
sendCoordinates([])
|
153 |
+
}
|
154 |
+
|
155 |
+
// Send the coordinates of the rectangle back to streamlit.
|
156 |
+
const sendCoordinates = (returnLabels: string[]) => {
|
157 |
+
setLabels(returnLabels)
|
158 |
+
const rects = canvas.getObjects().map((rect, i) => ({
|
159 |
+
...rect.getBoundingRect(),
|
160 |
+
label: returnLabels[i],
|
161 |
+
}))
|
162 |
+
Streamlit.setComponentValue({ rects })
|
163 |
+
}
|
164 |
+
|
165 |
+
// Update the bounding boxes when modified
|
166 |
+
useEffect(() => {
|
167 |
+
if (!canvas) {
|
168 |
+
return
|
169 |
+
}
|
170 |
+
const handleEvent = () => {
|
171 |
+
canvas.renderAll()
|
172 |
+
sendCoordinates(labels)
|
173 |
+
}
|
174 |
+
|
175 |
+
canvas.on("object:modified", handleEvent)
|
176 |
+
return () => {
|
177 |
+
canvas.off("object:modified")
|
178 |
+
}
|
179 |
+
})
|
180 |
+
|
181 |
+
// Adjust the theme according to the system
|
182 |
+
const onSelectMode = (mode: string) => {
|
183 |
+
setMode(mode)
|
184 |
+
if (mode === "dark") document.body.classList.add("dark-mode")
|
185 |
+
else document.body.classList.remove("dark-mode")
|
186 |
+
}
|
187 |
+
|
188 |
+
useEffect(() => {
|
189 |
+
// Add listener to update styles
|
190 |
+
window
|
191 |
+
.matchMedia("(prefers-color-scheme: dark)")
|
192 |
+
.addEventListener("change", (e) =>
|
193 |
+
onSelectMode(e.matches ? "dark" : "light")
|
194 |
+
)
|
195 |
+
|
196 |
+
// Setup dark/light mode for the first time
|
197 |
+
onSelectMode(
|
198 |
+
window.matchMedia("(prefers-color-scheme: dark)").matches
|
199 |
+
? "dark"
|
200 |
+
: "light"
|
201 |
+
)
|
202 |
+
|
203 |
+
// Remove listener
|
204 |
+
return () => {
|
205 |
+
window
|
206 |
+
.matchMedia("(prefers-color-scheme: dark)")
|
207 |
+
.removeEventListener("change", () => {})
|
208 |
+
}
|
209 |
+
}, [])
|
210 |
+
|
211 |
+
return (
|
212 |
+
<>
|
213 |
+
<canvas
|
214 |
+
id="c"
|
215 |
+
className={mode === "dark" ? styles.dark : ""}
|
216 |
+
width={canvasWidth}
|
217 |
+
height={canvasHeight}
|
218 |
+
/>
|
219 |
+
<div className={mode === "dark" ? styles.dark : ""}>
|
220 |
+
<button
|
221 |
+
className={mode === "dark" ? styles.dark : ""}
|
222 |
+
onClick={addBoxHandler}
|
223 |
+
>
|
224 |
+
Add bounding box
|
225 |
+
</button>
|
226 |
+
<button
|
227 |
+
className={mode === "dark" ? styles.dark : ""}
|
228 |
+
onClick={removeBoxHandler}
|
229 |
+
>
|
230 |
+
Remove select
|
231 |
+
</button>
|
232 |
+
<button
|
233 |
+
className={mode === "dark" ? styles.dark : ""}
|
234 |
+
onClick={resetHandler}
|
235 |
+
>
|
236 |
+
Reset
|
237 |
+
</button>
|
238 |
+
<button
|
239 |
+
className={mode === "dark" ? styles.dark : ""}
|
240 |
+
onClick={clearHandler}
|
241 |
+
>
|
242 |
+
Clear all
|
243 |
+
</button>
|
244 |
+
</div>
|
245 |
+
</>
|
246 |
+
)
|
247 |
+
}
|
248 |
+
|
249 |
+
export default withStreamlitConnection(StreamlitImgLabel)
|
Pages/streamlit_img_label/frontend/src/index.tsx
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from "react"
|
2 |
+
import ReactDOM from "react-dom"
|
3 |
+
import StreamlitImgLabel from "./StreamlitImgLabel"
|
4 |
+
|
5 |
+
ReactDOM.render(
|
6 |
+
<React.StrictMode>
|
7 |
+
<StreamlitImgLabel />
|
8 |
+
</React.StrictMode>,
|
9 |
+
document.getElementById("root")
|
10 |
+
)
|
Pages/streamlit_img_label/frontend/src/react-app-env.d.ts
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
/// <reference types="react-scripts" />
|
Pages/streamlit_img_label/frontend/tsconfig.json
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "es5",
|
4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
5 |
+
"allowJs": true,
|
6 |
+
"skipLibCheck": true,
|
7 |
+
"esModuleInterop": true,
|
8 |
+
"allowSyntheticDefaultImports": true,
|
9 |
+
"strict": true,
|
10 |
+
"forceConsistentCasingInFileNames": true,
|
11 |
+
"module": "esnext",
|
12 |
+
"moduleResolution": "node",
|
13 |
+
"resolveJsonModule": true,
|
14 |
+
"isolatedModules": true,
|
15 |
+
"noEmit": true,
|
16 |
+
"jsx": "react"
|
17 |
+
},
|
18 |
+
"include": ["src"]
|
19 |
+
}
|
Pages/streamlit_img_label/manage.py
ADDED
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import re
|
3 |
+
import numpy as np
|
4 |
+
from PIL import Image
|
5 |
+
from .annotation import output_xml, read_xml
|
6 |
+
|
7 |
+
"""
|
8 |
+
.. module:: streamlit_img_label
|
9 |
+
:synopsis: manage.
|
10 |
+
.. moduleauthor:: Tianning Li <ltianningli@gmail.com>
|
11 |
+
"""
|
12 |
+
|
13 |
+
|
14 |
+
class ImageManager:
|
15 |
+
"""ImageManager
|
16 |
+
Manage the image object.
|
17 |
+
|
18 |
+
Args:
|
19 |
+
filename(str): the image file.
|
20 |
+
"""
|
21 |
+
|
22 |
+
def __init__(self, filename):
|
23 |
+
"""initiate module"""
|
24 |
+
self._filename = filename
|
25 |
+
self._img = Image.open(filename)
|
26 |
+
self._rects = []
|
27 |
+
self._load_rects()
|
28 |
+
self._resized_ratio_w = 1
|
29 |
+
self._resized_ratio_h = 1
|
30 |
+
|
31 |
+
def _load_rects(self):
|
32 |
+
rects_xml = read_xml(self._filename)
|
33 |
+
if rects_xml:
|
34 |
+
self._rects = rects_xml
|
35 |
+
|
36 |
+
def get_img(self):
|
37 |
+
"""get the image object
|
38 |
+
|
39 |
+
Returns:
|
40 |
+
img(PIL.Image): the image object.
|
41 |
+
"""
|
42 |
+
return self._img
|
43 |
+
|
44 |
+
def get_rects(self):
|
45 |
+
"""get the rects
|
46 |
+
|
47 |
+
Returns:
|
48 |
+
rects(list): the bounding boxes of the image.
|
49 |
+
"""
|
50 |
+
return self._rects
|
51 |
+
|
52 |
+
def resizing_img(self, max_height=700, max_width=700):
|
53 |
+
"""resizing the image by max_height and max_width.
|
54 |
+
|
55 |
+
Args:
|
56 |
+
max_height(int): the max_height of the frame.
|
57 |
+
max_width(int): the max_width of the frame.
|
58 |
+
Returns:
|
59 |
+
resized_img(PIL.Image): the resized image.
|
60 |
+
"""
|
61 |
+
resized_img = self._img.copy()
|
62 |
+
if resized_img.height > max_height:
|
63 |
+
ratio = max_height / resized_img.height
|
64 |
+
resized_img = resized_img.resize(
|
65 |
+
(int(resized_img.width * ratio), int(resized_img.height * ratio))
|
66 |
+
)
|
67 |
+
if resized_img.width > max_width:
|
68 |
+
ratio = max_width / resized_img.width
|
69 |
+
resized_img = resized_img.resize(
|
70 |
+
(int(resized_img.width * ratio), int(resized_img.height * ratio))
|
71 |
+
)
|
72 |
+
|
73 |
+
self._resized_ratio_w = self._img.width / resized_img.width
|
74 |
+
self._resized_ratio_h = self._img.height / resized_img.height
|
75 |
+
return resized_img
|
76 |
+
|
77 |
+
def _resize_rect(self, rect):
|
78 |
+
resized_rect = {}
|
79 |
+
resized_rect["left"] = rect["left"] / self._resized_ratio_w
|
80 |
+
resized_rect["width"] = rect["width"] / self._resized_ratio_w
|
81 |
+
resized_rect["top"] = rect["top"] / self._resized_ratio_h
|
82 |
+
resized_rect["height"] = rect["height"] / self._resized_ratio_h
|
83 |
+
if "label" in rect:
|
84 |
+
resized_rect["label"] = rect["label"]
|
85 |
+
return resized_rect
|
86 |
+
|
87 |
+
def get_resized_rects(self):
|
88 |
+
"""get resized the rects according to the resized image.
|
89 |
+
|
90 |
+
Returns:
|
91 |
+
resized_rects(list): the resized bounding boxes of the image.
|
92 |
+
"""
|
93 |
+
return [self._resize_rect(rect) for rect in self._rects]
|
94 |
+
|
95 |
+
def _chop_box_img(self, rect):
|
96 |
+
rect["left"] = int(rect["left"] * self._resized_ratio_w)
|
97 |
+
rect["width"] = int(rect["width"] * self._resized_ratio_w)
|
98 |
+
rect["top"] = int(rect["top"] * self._resized_ratio_h)
|
99 |
+
rect["height"] = int(rect["height"] * self._resized_ratio_h)
|
100 |
+
left, top, width, height = (
|
101 |
+
rect["left"],
|
102 |
+
rect["top"],
|
103 |
+
rect["width"],
|
104 |
+
rect["height"],
|
105 |
+
)
|
106 |
+
|
107 |
+
raw_image = np.asarray(self._img).astype("uint8")
|
108 |
+
prev_img = np.zeros(raw_image.shape, dtype="uint8")
|
109 |
+
prev_img[top : top + height, left : left + width] = raw_image[
|
110 |
+
top : top + height, left : left + width
|
111 |
+
]
|
112 |
+
prev_img = prev_img[top : top + height, left : left + width]
|
113 |
+
label = ""
|
114 |
+
if "label" in rect:
|
115 |
+
label = rect["label"]
|
116 |
+
return (Image.fromarray(prev_img), label)
|
117 |
+
|
118 |
+
def init_annotation(self, rects):
|
119 |
+
"""init annotation for current rects.
|
120 |
+
|
121 |
+
Args:
|
122 |
+
rects(list): the bounding boxes of the image.
|
123 |
+
Returns:
|
124 |
+
prev_img(list): list of preview images with default label.
|
125 |
+
"""
|
126 |
+
self._current_rects = rects
|
127 |
+
return [self._chop_box_img(rect) for rect in self._current_rects]
|
128 |
+
|
129 |
+
def set_annotation(self, index, label):
|
130 |
+
"""set the label of the image.
|
131 |
+
|
132 |
+
Args:
|
133 |
+
index(int): the index of the list of bounding boxes of the image.
|
134 |
+
label(str): the label of the bounding box
|
135 |
+
"""
|
136 |
+
self._current_rects[index]["label"] = label
|
137 |
+
|
138 |
+
def save_annotation(self):
|
139 |
+
"""output the xml annotation file."""
|
140 |
+
output_xml(self._filename, self._img, self._current_rects)
|
141 |
+
|
142 |
+
|
143 |
+
class ImageDirManager:
|
144 |
+
def __init__(self, dir_name):
|
145 |
+
self._dir_name = dir_name
|
146 |
+
self._files = []
|
147 |
+
self._annotations_files = []
|
148 |
+
|
149 |
+
def get_all_files(self, allow_types=["png", "jpg", "jpeg"]):
|
150 |
+
allow_types += [i.upper() for i in allow_types]
|
151 |
+
mask = ".*\.[" + "|".join(allow_types) + "]"
|
152 |
+
self._files = [
|
153 |
+
file for file in os.listdir(self._dir_name) if re.match(mask, file)
|
154 |
+
]
|
155 |
+
return self._files
|
156 |
+
|
157 |
+
def get_exist_annotation_files(self):
|
158 |
+
self._annotations_files = [
|
159 |
+
file for file in os.listdir(self._dir_name) if re.match(".*.xml", file)
|
160 |
+
]
|
161 |
+
return self._annotations_files
|
162 |
+
|
163 |
+
def set_all_files(self, files):
|
164 |
+
self._files = files
|
165 |
+
|
166 |
+
def set_annotation_files(self, files):
|
167 |
+
self._annotations_files = files
|
168 |
+
|
169 |
+
def get_image(self, index):
|
170 |
+
return self._files[index]
|
171 |
+
|
172 |
+
def _get_next_image_helper(self, index):
|
173 |
+
while index < len(self._files) - 1:
|
174 |
+
index += 1
|
175 |
+
image_file = self._files[index]
|
176 |
+
image_file_name = image_file.split(".")[0]
|
177 |
+
if f"{image_file_name}.xml" not in self._annotations_files:
|
178 |
+
return index
|
179 |
+
return None
|
180 |
+
|
181 |
+
def get_next_annotation_image(self, index):
|
182 |
+
image_index = self._get_next_image_helper(index)
|
183 |
+
if image_index:
|
184 |
+
return image_index
|
185 |
+
if not image_index and len(self._files) != len(self._annotations_files):
|
186 |
+
return self._get_next_image_helper(0)
|
{App β Pages}/utils.py
RENAMED
File without changes
|
app.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1 |
import streamlit as st
|
2 |
-
from
|
3 |
-
from
|
4 |
-
from
|
|
|
5 |
|
6 |
|
7 |
st.set_page_config(
|
@@ -14,13 +15,15 @@ st.set_page_config(
|
|
14 |
|
15 |
def MAIN():
|
16 |
st.sidebar.title('LumiereIQ')
|
17 |
-
app = st.sidebar.selectbox('', ['Create Visuals','Background Images', 'About Me'])
|
18 |
if app == "Create Visuals":
|
19 |
CREATEVISUALS()
|
20 |
elif app == "About Me":
|
21 |
ABOUTUS()
|
22 |
elif app == "Background Images":
|
23 |
BGIMAGES()
|
|
|
|
|
24 |
|
25 |
|
26 |
MAIN()
|
|
|
1 |
import streamlit as st
|
2 |
+
from Pages.createVisual import CREATEVISUALS
|
3 |
+
from Pages.AboutMe import ABOUTUS
|
4 |
+
from Pages.bgImages import BGIMAGES
|
5 |
+
from Pages.createVideo import CREATEGIF
|
6 |
|
7 |
|
8 |
st.set_page_config(
|
|
|
15 |
|
16 |
def MAIN():
|
17 |
st.sidebar.title('LumiereIQ')
|
18 |
+
app = st.sidebar.selectbox('', ['Create Visuals','Background Images','Create Video', 'About Me'])
|
19 |
if app == "Create Visuals":
|
20 |
CREATEVISUALS()
|
21 |
elif app == "About Me":
|
22 |
ABOUTUS()
|
23 |
elif app == "Background Images":
|
24 |
BGIMAGES()
|
25 |
+
elif app == "Create Video":
|
26 |
+
CREATEGIF()
|
27 |
|
28 |
|
29 |
MAIN()
|
output/overlay_image.png
ADDED
requirements.txt
CHANGED
@@ -1,3 +1,9 @@
|
|
1 |
streamlit
|
2 |
diffusers
|
3 |
-
torch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
streamlit
|
2 |
diffusers
|
3 |
+
torch
|
4 |
+
transformers
|
5 |
+
git+https://github.com/facebookresearch/segment-anything.git
|
6 |
+
pascal_voc_writer
|
7 |
+
roboflow
|
8 |
+
dataclasses-json
|
9 |
+
supervision
|
saved_masks/inverted_mask_0.png
ADDED
setup.sh
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
|
3 |
+
# Create the directory for weights if it doesn't exist
|
4 |
+
mkdir -p weights
|
5 |
+
|
6 |
+
# Download the file
|
7 |
+
wget -q https://dl.fbaipublicfiles.com/segment_anything/sam_vit_h_4b8939.pth -P weights
|
8 |
+
|
9 |
+
|
uploaded_images/download.png
ADDED