ardaatahan commited on
Commit
dde4343
·
verified ·
1 Parent(s): 1d51105

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. .DS_Store +0 -0
  2. README.md +79 -8
  3. requirements.txt +5 -0
  4. stack_overflow_users.py +218 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
README.md CHANGED
@@ -1,12 +1,83 @@
1
  ---
2
- title: Stack Overflow Users
3
- emoji: 🐠
4
- colorFrom: red
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 4.28.3
8
- app_file: app.py
9
- pinned: false
10
  ---
 
 
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: stack_overflow_users
3
+ app_file: stack_overflow_users.py
 
 
4
  sdk: gradio
5
+ sdk_version: 4.28.2
 
 
6
  ---
7
+ # Stack Overflow User Profiles and Face Detection
8
+ The live application demo can be found [here](https://9ad3e3e6958cd392e8.gradio.live).
9
 
10
+
11
+ ## About The Project
12
+ This is a simple Gradio application that fetches the top 10 user profiles based on reputation from Stack Overflow API, downloads their profile images, attempts to detect faces within those images using `dlib`, and presents them in an intuitive user interface.
13
+
14
+
15
+ ### Features
16
+ * Fetch user profiles from Stack Overflow.
17
+ * Download profile images asynchronously.
18
+ * Detect faces in the images using the `dlib` library.
19
+ * Display the results in a user-friendly web interface using Gradio.
20
+
21
+
22
+ ## Installation
23
+
24
+ ### Prerequisites
25
+ * Python 3.8 or higher
26
+ * pip3
27
+
28
+ ### Installing Dependencies
29
+ 1. Clone the repository:
30
+ ```sh
31
+ git clone https://github.com/ardaatahan/technical-challenge.git
32
+ cd technical-challenge
33
+ ```
34
+
35
+ 2. dlib requires cmake. If cmake is not installed:
36
+ ```sh
37
+ brew install cmake
38
+ ```
39
+ or you can follow [this](https://cgold.readthedocs.io/en/latest/first-step/installation.html) guide if you are using a different package manager/OS.
40
+
41
+ 3. Install the required packages:
42
+ ```sh
43
+ pip3 install -r requirements.txt
44
+ ```
45
+
46
+ ### Usage
47
+ To run the project:
48
+
49
+ 1. Start the application:
50
+ ```sh
51
+ python3 stack_overflow_users.py
52
+ ```
53
+
54
+ 2. Open your web browser and navigate to the link provided by Gradio.
55
+
56
+ 3. Press Generate button to fetch users and display results or Clear button to clear the results.
57
+
58
+ ### How It Works
59
+ 1. The application makes a call to the Stack Overflow API to fetch the top 10 user profiles.
60
+ 2. It then asynchronously downloads the profile images of these users.
61
+ 3. Each image is processed to detect faces using the `dlib` library.
62
+ 4. Results are displayed in a Gradio interface, showing the user details along with their profile image marked with detected faces.
63
+
64
+ ### Third Party Libraries Used
65
+ `numpy`:
66
+ * Use Case: Used extensively for handling arrays, which is crucial when manipulating image data received from profile images. numpy allows efficient operations on images, such as transformations and decoding, which are essential for the image processing steps prior to applying face detection algorithms.
67
+ * Why Chosen: Chosen for its performance and wide support in the Python ecosystem, especially in data manipulation and numerical operations which are fundamental in image processing tasks.
68
+
69
+ `gradio`:
70
+ * Use Case: Facilitates the creation of a user-friendly interface that displays the Stack Overflow profiles and the results of the face detection process. Gradio makes it straightforward to create interactive blocks for displaying images and text, which are essential for the output of this project.
71
+ * Why Chosen: Gradio is particularly effective for creating demo applications quickly and with minimal code. It supports a wide range of inputs and outputs, making it ideal for projects that require showcasing machine learning or image processing results in an intuitive way.
72
+
73
+ `aiohttp`:
74
+ * Use Case: Used to handle asynchronous HTTP requests to the Stack Overflow API. This is important for non-blocking data retrieval, allowing the application to perform other tasks, such as processing images, while waiting for HTTP responses.
75
+ * Why Chosen: aiohttp is chosen for its ability to handle asynchronous web requests efficiently, which helps in maintaining responsiveness and scalability of the application when dealing with I/O-bound tasks like network requests.
76
+
77
+ `opencv`:
78
+ * Use Case: Provides a variety of image processing functionalities which are used to decode images from bytes, convert image formats, and prepare images for face detection. The headless version is used to reduce the overhead since GUI features of OpenCV are not needed.
79
+ * Why Chosen: The headless version of OpenCV reduces the installation footprint and removes unnecessary dependencies on GUI libraries, which is beneficial for deployment in server environments or containers where graphical user interfaces are not utilized.
80
+
81
+ `dlib`:
82
+ * Use Case: Employs robust machine learning algorithms to detect faces in images. dlib offers one of the most effective and accurate face detection models available, making it suitable for projects that require reliable facial recognition.
83
+ * Why Chosen: Despite its complex setup, dlib is selected for its superior performance in face detection compared to other libraries. Its face detector is based on a modification of the standard Histogram of Oriented Gradients (HOG) + Linear SVM method, which is well-suited for this application given its accuracy and efficiency in detecting human faces.
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ aiohttp==3.9.5
2
+ dlib==19.24.4
3
+ gradio==4.28.3
4
+ numpy==1.26.4
5
+ opencv_python==4.9.0.80
stack_overflow_users.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import gradio as gr
3
+
4
+ import aiohttp
5
+ import asyncio
6
+ import cv2
7
+ import dlib
8
+ import base64
9
+ import ssl
10
+
11
+
12
+ ssl._create_default_https_context = ssl._create_unverified_context
13
+
14
+
15
+ URL = "https://api.stackexchange.com/2.2/users?site=stackoverflow"
16
+ MAX_USERS = 10
17
+
18
+
19
+ def filter_profile_data(data):
20
+ """
21
+ Filter user profile data to get reputation, display name, profile link, and profile image
22
+ of first 10 users based on reputation.
23
+
24
+ :param data: array of dicts containing raw user profile information
25
+ :return: array of dicts filtered user profile information
26
+ """
27
+ data = data["items"]
28
+ data = data[: min(MAX_USERS, len(data))]
29
+ keys_to_keep = ["reputation", "location", "display_name", "link", "profile_image"]
30
+ filtered_data = []
31
+ for raw_user in data:
32
+ user = {}
33
+ for key in keys_to_keep:
34
+ user[key] = raw_user[key] if key in raw_user else None
35
+ filtered_data.append(user)
36
+ return filtered_data
37
+
38
+
39
+ async def fetch_stack_overflow_profiles(url):
40
+ """
41
+ Fetch user profiles from Stack Overflow API.
42
+
43
+ :param url: URL of API to be called
44
+ :return: Raw user profile data if response is successful else error message
45
+ """
46
+ async with aiohttp.ClientSession() as session:
47
+ try:
48
+ async with session.get(url, timeout=10) as response:
49
+ if response.status == 200:
50
+ data = await response.json()
51
+ data = filter_profile_data(data)
52
+ return data
53
+ else:
54
+ return f"Failed to retrieve Stack Overflow user data: Status Code {response.status}"
55
+ except aiohttp.ClientError as e:
56
+ return str(e)
57
+ except asyncio.TimeoutError:
58
+ return "User data request timed out"
59
+
60
+
61
+ async def download_profile_image(url):
62
+ """
63
+ Download profile image from given URL.
64
+
65
+ :param url: URL of profile image
66
+ :return: RGB decoded image if image can be downloaded else error message
67
+ """
68
+ async with aiohttp.ClientSession() as session:
69
+ try:
70
+ async with session.get(url, timeout=10) as response:
71
+ if response.status == 200:
72
+ data = await response.read()
73
+ array = np.asarray(bytearray(data), dtype=np.uint8)
74
+ image = cv2.imdecode(array, 1)
75
+ return image
76
+ else:
77
+ return f"Failed to retrieve user profile image: Status Code {response.status}"
78
+ except aiohttp.ClientError as e:
79
+ return f"Profile image could not be downloaded: {str(e)}"
80
+ except asyncio.TimeoutError:
81
+ return "User profile image request timed out"
82
+
83
+
84
+ def detect_face_in_image(image):
85
+ """
86
+ Detects whether a there is a face in the input image using dlib.
87
+
88
+ :param image: Image to check for face
89
+ :return: Image with bounding box highlighting face if face exists else original image
90
+ :return: True if face exists in image else False
91
+ """
92
+ detector = dlib.get_frontal_face_detector()
93
+ faces, _, _ = detector.run(image, 1, -0.5)
94
+ for face in faces:
95
+ cv2.rectangle(
96
+ image,
97
+ (face.left(), face.top()),
98
+ (face.right(), face.bottom()),
99
+ (0, 255, 0),
100
+ 4,
101
+ )
102
+ _, buffer = cv2.imencode(".jpg", image)
103
+ image = base64.b64encode(buffer)
104
+ return image, len(faces) > 0
105
+
106
+
107
+ def fetch_and_process_users():
108
+ """
109
+ Higher level function to fetch users and analyze their profile images.
110
+
111
+ :return: HTML content displaying fetched user information and error messages as necessary
112
+ """
113
+ profiles = asyncio.run(fetch_stack_overflow_profiles(URL))
114
+ if type(profiles) is str:
115
+ return get_error_html(profiles)
116
+
117
+ user_content = []
118
+
119
+ for profile in profiles:
120
+ image_url = profile["profile_image"]
121
+ image = asyncio.run(download_profile_image(image_url)) if image_url else None
122
+
123
+ if image is None:
124
+ user_html = get_user_html(None, profile, "", "Image for this user could not be fetched!")
125
+ user_content.append(user_html)
126
+ continue
127
+
128
+ if type(image) is str:
129
+ user_html = get_user_html(None, profile, "", image)
130
+ user_content.append(user_html)
131
+ continue
132
+
133
+ image, face_exists = detect_face_in_image(image)
134
+ face_message = (
135
+ "Face detected and highlighted in the user profile image!"
136
+ if face_exists
137
+ else "No face detected in the user profile image!"
138
+ )
139
+ image = image.decode("utf-8")
140
+
141
+ user_html = get_user_html(image, profile, face_message, None)
142
+ user_content.append(user_html)
143
+
144
+ return "".join(user_content)
145
+
146
+
147
+ def get_error_html(error_message):
148
+ """
149
+ Constructs an HTML template to display error message.
150
+
151
+ :param error_message: Message to be displayed
152
+ :return: HTML code with the error message
153
+ """
154
+ return f"""
155
+ <div style='display: flex; flex-direction: column; align-items: center; margin-bottom: 5rem;'>
156
+ <div style='text-align: center; font-size: 16px;'>
157
+ <strong style='font-size: 18px;'></strong> {error_message} Try generating again.
158
+ <br>
159
+ </div>
160
+ </div>
161
+ """
162
+
163
+
164
+ def get_user_html(image, profile, face_message, error_message):
165
+ """
166
+ Constructs an HTML template to display user information and error message.
167
+
168
+ :param image: Base64 image to be displayed on the page
169
+ :param profile: Profile information to be displayed
170
+ :param face_message: Message that indicates whether face exists or not in the picture
171
+ :param error_message: Error message to display in case image cannot be fetched
172
+ :return: HTML code with the user information with a potential error message
173
+ """
174
+ return f"""
175
+ <div style='display: flex; flex-direction: column; align-items: center; margin-bottom: 5rem;'>
176
+ {f"""<div style='margin-bottom: 1rem;'>
177
+ <img src="data:image/jpeg;base64,{image}" alt="Profile image" style='width: 100%; height: 100%; object-fit: cover;'>
178
+ </div>""" if error_message is None else f"""<div style='text-align: center; font-size: 16px; margin-bottom: 1rem;'>
179
+ <strong style='font-size: 18px;'></strong> {error_message} Try generating again.
180
+ <br>
181
+ </div>"""}
182
+ <div style='text-align: center; font-size: 16px;'>
183
+ <strong style='font-size: 18px;'>Name:</strong> {profile.get("display_name", "Not available")}
184
+ <br>
185
+ <strong style='font-size: 18px;'>Reputation:</strong> {profile.get("reputation", "Not available")}
186
+ <br>
187
+ <strong style='font-size: 18px;'>Location:</strong> {profile.get("location", "Not available")}
188
+ <br>
189
+ <a href="{profile.get("link", "")}" target="_blank" style='font-size: 16px; color: blue; text-decoration: none;'>View Profile</a>
190
+ {f"""<br>
191
+ <span style='font-size: 16px; color: grey;'>{face_message}</span>""" if error_message is None else """"""}
192
+ </div>
193
+ </div>
194
+ """
195
+
196
+
197
+ def get_html_content():
198
+ """
199
+ Constructs the whole HTML template to be displayed to the user.
200
+
201
+ :return: HTML code to display each user's information and error messages
202
+ """
203
+ html_content = fetch_and_process_users()
204
+ return html_content
205
+
206
+
207
+ demo = gr.Interface(
208
+ fn=get_html_content,
209
+ inputs=[],
210
+ outputs=gr.components.HTML(label="Stack Overflow User Profiles"),
211
+ title="Stack Overflow User Profiles and Face Detection",
212
+ css="footer{display:none !important}",
213
+ allow_flagging="never",
214
+ )
215
+
216
+
217
+ if __name__ == "__main__":
218
+ demo.launch()