-
Notifications
You must be signed in to change notification settings - Fork 1
/
sniffer.py
290 lines (245 loc) · 12.4 KB
/
sniffer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
import streamlit as st
import streamlit.components.v1 as components
import pandas as pd
import numpy as np
from PIL import Image
from datetime import datetime
st.set_page_config(
page_title="Sniffer",
page_icon="🐕",
layout="centered",
initial_sidebar_state="collapsed",
menu_items={
'Get Help': "https://github.com/2320sharon/Streamlit_Sniffer",
'Report a bug': "https://github.com/2320sharon/Streamlit_Sniffer/issues",
'About': "# Sniffer. Sort your *extremely* cool images!"
}
)
def get_percent_blk_pixels(file):
img = Image.open(file)
img_array = np.array(img)
w, h = img_array.shape[0:2]
total_pixels = w * h if img_array.ndim < 3 else w * h * img_array.shape[2]
black_count = np.sum(img_array == 0)
return np.round(black_count / total_pixels, 2)
with st.expander("Upload Images", expanded=True):
uploaded_files = st.file_uploader("Choose a jpg file", accept_multiple_files=True)
for uploaded_file in uploaded_files:
bytes_data = uploaded_file.read()
images_list = uploaded_files
def create_csv_name(csv_filename: str = None) -> str:
today = datetime.now()
if csv_filename is not None:
if not csv_filename.endswith(".csv"):
csv_filename += ".csv"
elif csv_filename is None:
d1 = today.strftime("%d_%m_%Y_hr_%H_%M")
csv_filename = f"Sniffer_Output_" + d1 + ".csv"
return csv_filename
# Initialize Sniffer's states
if 'img_idx' not in st.session_state:
st.session_state.img_idx = 0
if 'undo_indexes' not in st.session_state:
st.session_state.undo_indexes = [0]
if 'df' not in st.session_state:
st.session_state.df = pd.DataFrame(columns=['Filename', 'Sorted'])
if st.session_state.img_idx > (len(images_list) + 2):
st.session_state.img_idx = (len(images_list) - 1) if (len(images_list) - 1) > 0 else 0
def create_csv():
return st.session_state.df.to_csv(index=False).encode('utf-8')
def increment_index(blk_percent : int, blk_filter_enabled :bool ) -> None:
""" increments st.session_state.img_idx to next valid image index
Args:
blk_percent (int): percentage of black pixels allowed
blk_filter_enabled (bool): True if black pixel filter is enabled
"""
# Add the current index as a valid index to list of valid index to undo to
st.session_state.undo_indexes.append(st.session_state.img_idx)
st.session_state.img_idx += 1
# Only check for black pixel if the black_pixel_filter is enabled
if blk_filter_enabled:
if -1 < st.session_state.img_idx <= (len(images_list) - 1):
if get_percent_blk_pixels(images_list[st.session_state.img_idx]) >= blk_percent:
# Continue incrementing index while in range and percnt_blk_pixels > limit
while (-1 < st.session_state.img_idx <= (len(images_list) - 1)
) and (get_percent_blk_pixels(images_list[st.session_state.img_idx]) >= blk_percent):
st.session_state.img_idx += 1
index_out_of_range(st.session_state.img_idx, len(images_list))
def index_out_of_range(idx: int, length: int):
""" Handles all instances when the index is out of range of the images list
Args:
idx (int): current index
length (int): length of the images list
"""
if idx == (length):
st.success('All images have been sorted!')
st.balloons()
else:
st.warning(f'No more images to sort {idx} /{length} ')
def next_button(**kwargs):
blk_percent = kwargs["blk_percent"]
blk_filter_enabled = kwargs["blk_filter_enabled"]
rating = None
if "rating" in kwargs:
rating = kwargs["rating"]
print(rating)
if rating is None:
raise Exception("Missing the rating from the scale.")
if -1 < st.session_state.img_idx <= (len(images_list) - 1):
if blk_filter_enabled:
if get_percent_blk_pixels(images_list[st.session_state.img_idx]) < blk_percent:
row = {"Filename": images_list[st.session_state.img_idx].name, 'Sorted': rating}
st.session_state.df = pd.concat([st.session_state.df, pd.DataFrame.from_records([row])], ignore_index=True)
else:
row = {"Filename": images_list[st.session_state.img_idx].name, 'Sorted': rating}
st.session_state.df = pd.concat([st.session_state.df, pd.DataFrame.from_records([row])], ignore_index=True)
increment_index( blk_percent,blk_filter_enabled)
else:
# Handles all cases when index is out of range
index_out_of_range(st.session_state.img_idx, len(images_list))
def yes_button(**kwargs):
blk_percent = kwargs["blk_percent"]
blk_filter_enabled = kwargs["blk_filter_enabled"]
if -1 < st.session_state.img_idx <= (len(images_list) - 1):
if blk_filter_enabled:
if get_percent_blk_pixels(images_list[st.session_state.img_idx]) < blk_percent:
row = {"Filename": images_list[st.session_state.img_idx].name, 'Sorted': "good"}
st.session_state.df = pd.concat([st.session_state.df, pd.DataFrame.from_records([row])], ignore_index=True)
else:
row = {"Filename": images_list[st.session_state.img_idx].name, 'Sorted': "good"}
st.session_state.df = pd.concat([st.session_state.df, pd.DataFrame.from_records([row])], ignore_index=True)
increment_index( blk_percent,blk_filter_enabled)
else:
# Handles all cases when index is out of range
index_out_of_range(st.session_state.img_idx, len(images_list))
def no_button(**kwargs):
blk_percent = kwargs["blk_percent"]
blk_filter_enabled = kwargs["blk_filter_enabled"]
if -1 < st.session_state.img_idx <= (len(images_list) - 1):
if blk_filter_enabled:
if get_percent_blk_pixels(images_list[st.session_state.img_idx]) < blk_percent:
row = {"Filename": images_list[st.session_state.img_idx].name, 'Sorted': "bad"}
st.session_state.df = pd.concat([st.session_state.df, pd.DataFrame.from_records([row])], ignore_index=True)
else:
row = {"Filename": images_list[st.session_state.img_idx].name, 'Sorted': "bad"}
st.session_state.df = pd.concat([st.session_state.df, pd.DataFrame.from_records([row])], ignore_index=True)
increment_index(blk_percent,blk_filter_enabled)
else:
# Handles all cases when index is out of range
index_out_of_range(st.session_state.img_idx, len(images_list))
def undo_button():
# Pop the last index from undo_indexes only when its not empty
if len(st.session_state.undo_indexes) > 0:
st.session_state.img_idx = st.session_state.undo_indexes.pop()
# Ensure the list of available undo indexes are never empty
if len(st.session_state.undo_indexes) == 0:
st.session_state.undo_indexes.append(0)
# Remove filename from the dataframe
if st.session_state.img_idx >= 0:
if images_list != [] and st.session_state.img_idx <= (len(images_list) - 1):
drop_filename = images_list[st.session_state.img_idx].name
drop_index = st.session_state.df.loc[st.session_state.df['Filename'] == drop_filename].index.values
st.session_state.df.drop(drop_index, axis=0, inplace=True)
else:
st.warning('Cannot Undo')
st.title("Sniffer🐕")
st.image("./assets/sniffer.jpg")
# Sets num_image=1 if images_list is empty
num_images = (len(images_list)) if (len(images_list)) > 0 else 1
try:
my_bar = st.progress((st.session_state.img_idx) / num_images)
except st.StreamlitAPIException:
my_bar = st.progress(0)
# Radio buttons allow user to choose how they will rate images
scale_str = 'Scale'
yes_no_str = 'Yes No Buttons'
with st.expander("Choose Image Controls", expanded=True):
rating_method = st.radio(label="Method to rate images",options =(scale_str,yes_no_str))
# Interface for image controls
control_col_1, control_col_2= st.columns([1,1])
with control_col_1:
blk_percent=50.0
blk_filter_enabled=st.checkbox("Enable the Black Pixel Filter?",value=True,key="blk_filter_enabled")
if blk_filter_enabled:
st.write("Filter out images where the number of black pixels exceeds the allowed percentage:")
blk_percent = st.slider("Percentage of Black Pixels Allowed:", step=0.01, value=0.50, min_value=0.0, max_value=1.0)
with control_col_2:
resize_allowed = False
show_resize_controls = st.checkbox(label='Show Resize Controls', value=False)
if show_resize_controls:
st.write("Adjust the sliders to the height and width of the image you want, then check resize.")
st.write("To return the image to the original size uncheck resize.")
height = st.slider('Height:', 200, 200, 1500, step=50)
width = st.slider('Width', 200, 200, 1500, step=50)
resize_allowed = st.checkbox(label='Resize', value=False)
# let user choose scale limits if they chose to use scale
if rating_method == scale_str:
with st.expander("Change slider range"):
slider_col1, slider_col2= st.columns([1,1])
with slider_col1:
min_slider=st.number_input(label="Minimum Slider Value",value=0)
with slider_col2:
max_slider=st.number_input(label="Maximum Slider Value",value=5)
# Interface to view images and rate images
col1, col2= st.columns([1,5])
with col1:
# if user chose to use scale to rate images render scale
if rating_method == scale_str:
scale_rating = st.slider('Rate Image', min_slider, max_slider, min_slider, step=1)
st.button(label="Next",on_click=next_button,
kwargs={"blk_percent": blk_percent,
"blk_filter_enabled":blk_filter_enabled,
"rating":scale_rating})
# if user chose to use yes/no buttons to rate images render yes/no buttons
if rating_method == yes_no_str:
st.button(label="Yes", key="yes_button", on_click=yes_button, kwargs={
"blk_percent": blk_percent,"blk_filter_enabled":blk_filter_enabled})
st.button(label="No", key="no_button", on_click=no_button, kwargs={
"blk_percent": blk_percent,"blk_filter_enabled":blk_filter_enabled})
# regardless of which option the user chooses off the undo button
st.button(label="Undo", key="undo_button", on_click=undo_button)
with col2:
st.write(f"{st.session_state.img_idx} of {num_images}")
if images_list == []:
image = Image.open("./assets/new_loading_sniffer.jpg")
else:
# Display done.jpg when all images are sorted
if st.session_state.img_idx >= len(images_list):
image = Image.open("./assets/done.jpg")
st.image(image, width=300)
else:
# Default value when the index is out of range
percent_blk_pixels=0
if -1 < st.session_state.img_idx <= (len(images_list) - 1):
# Display the percentage of black pixels in the image if the checkbox is enabled
if blk_filter_enabled:
percent_blk_pixels = get_percent_blk_pixels(images_list[st.session_state.img_idx])
st.write(f"Percentage of Black Pixels : {round(percent_blk_pixels*100,2)}%")
# Display warning msg if current percentage of black pixels exceeds limit
if percent_blk_pixels >= blk_percent:
increment_index(blk_percent,blk_filter_enabled)
if st.session_state.img_idx >= len(images_list):
image = Image.open("./assets/done.jpg")
st.image(image, width=300)
else:
# caption is "" when images_list is empty otherwise its image name
image = Image.open(images_list[st.session_state.img_idx])
if images_list == []:
caption = ''
else:
caption=f'#{st.session_state.img_idx} {images_list[st.session_state.img_idx].name}'
# resize image if user clicked resize allowed checkbox
if resize_allowed:
image = image.resize((width, height))
else:
width = 600
st.image(image, caption=caption, width=width)
st.download_button(
label="Download data as CSV 💻",
data=create_csv(),
file_name=create_csv_name(),
mime='text/csv',
)
with st.expander("See Dataset Details 📈"):
st.dataframe(st.session_state.df)
st.bar_chart(st.session_state.df['Sorted'].value_counts())