Thiago Hersan commited on
Commit
a4bef5f
·
0 Parent(s):

init commit

Browse files
Files changed (6) hide show
  1. .github/workflows/deploy-hf.yml +25 -0
  2. .gitignore +7 -0
  3. README.md +11 -0
  4. app.py +135 -0
  5. index.html +70 -0
  6. requirements.txt +3 -0
.github/workflows/deploy-hf.yml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy to Hugging Face spaces
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout Dev Repo
14
+ uses: actions/checkout@v3
15
+ with:
16
+ fetch-depth: 0
17
+ lfs: true
18
+
19
+ - name: Push to HF
20
+ env:
21
+ HFTOKEN: ${{ secrets.HFTOKEN }}
22
+
23
+ run: |
24
+ git remote add hf https://thiagohersan:$HFTOKEN@huggingface.co/spaces/visualizedata/PSAM5020-ColorSort-Gradio
25
+ git push -f hf main
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .DS_S*
2
+ __pycache__/
3
+ gradio_cached_examples/
4
+ .gradio/
5
+ .ipynb_checkpoints/
6
+ flowers.tar.gz
7
+ data/image
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: PSAM5020 Color Sort by Clustering
3
+ emoji: 🌸
4
+ colorFrom: blue
5
+ colorTo: pink
6
+ sdk: gradio
7
+ python_version: 3.10.12
8
+ sdk_version: 5.0.2
9
+ app_file: app.py
10
+ pinned: false
11
+ ---
app.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import numpy as np
4
+ import re
5
+ import tarfile
6
+
7
+ import PIL.Image as PImage
8
+
9
+ from os import listdir, path, remove
10
+ from sklearn.cluster import KMeans
11
+ from urllib import request
12
+
13
+ def download_extract():
14
+ url = "https://github.com/PSAM-5020-2025S-A/5020-utils/releases/latest/download/flowers.tar.gz"
15
+ target_path = "flowers.tar.gz"
16
+
17
+ with request.urlopen(request.Request(url), timeout=15.0) as response:
18
+ if response.status == 200:
19
+ with open(target_path, "wb") as f:
20
+ f.write(response.read())
21
+
22
+ tar = tarfile.open(target_path, "r:gz")
23
+ tar.extractall()
24
+ tar.close()
25
+ remove("flowers.tar.gz")
26
+
27
+ # Posterize image and get representative colors
28
+ def top_colors(fpath, n_clusters=8, n_colors=4):
29
+ pimg = PImage.open(fpath).convert("RGB")
30
+ pimg_pxs = list(pimg.getdata())
31
+
32
+ posterizer = KMeans(n_clusters=n_clusters)
33
+ px_clusters = posterizer.fit_predict(pimg_pxs)
34
+ cluster_colors = posterizer.cluster_centers_
35
+
36
+ _, ccounts = np.unique(px_clusters, return_counts=True)
37
+ ccounts_order = np.argsort(-ccounts)
38
+ ccolors_sorted = [[round(rgb) for rgb in cluster_colors[idx]] for idx in ccounts_order]
39
+
40
+ return ccolors_sorted[:n_colors]
41
+
42
+ # Cluster all images
43
+ def get_top_colors(flower_image_dir):
44
+ flower_files = sorted([f for f in listdir(flower_image_dir) if f.endswith(".png")])
45
+
46
+ file_colors = []
47
+ for fname in flower_files:
48
+ file_colors.append({
49
+ "filename": fname,
50
+ "colors": top_colors(f"{flower_image_dir}/{fname}", n_clusters=8, n_colors=4)
51
+ })
52
+ return file_colors
53
+
54
+ # Euclidean distance between 2 RGB color tuples
55
+ def color_distance(c0, c1):
56
+ return ((c0[0] - c1[0])**2 + (c0[1] - c1[1])**2 + (c0[2] - c1[2])**2) ** 0.5
57
+
58
+ # Function that returns minimum distance between a reference color and colors from a list
59
+ def min_color_distance(ref_color, color_list):
60
+ c_dists = [color_distance(ref_color, c) for c in color_list]
61
+ return min(c_dists)
62
+
63
+ # Turns a css color string in the form `#12AB56` or
64
+ # `rgb(18, 171, 87)` or
65
+ # `rgba(18, 171, 87, 1)`
66
+ # into an RGB list [18, 171, 87]
67
+ def css_to_rgb(css_str):
68
+ if css_str[0] == "#":
69
+ return [int(css_str[i:i+2], 16) for i in range(1,6,2)]
70
+
71
+ COLOR_PATTERN = r"([^(]+)\(([0-9.]+), ?([0-9.]+%?), ?([0-9.]+%?)(, ?([0-9.]+))?\)"
72
+ match = re.match(COLOR_PATTERN, css_str)
73
+ if not match:
74
+ return [0,0,0]
75
+
76
+ if "rgb" in match.group(1):
77
+ return [int(float(match.group(i))) for i in range(2,5)]
78
+
79
+ if "hsl" in match.group(1):
80
+ print("hsl not supported")
81
+ return [0,0,0]
82
+
83
+ def order_by_color(center_color_str):
84
+ center_color = css_to_rgb(center_color_str)
85
+
86
+ # Function that returns how close an image is to a given color
87
+ def by_color_dist(A):
88
+ return min_color_distance(center_color, A["colors"])
89
+
90
+ file_colors_sorted = sorted(FILE_COLORS, key=by_color_dist)
91
+ files_sorted = [A["filename"] for A in file_colors_sorted]
92
+
93
+ file_order = {
94
+ "color": center_color,
95
+ "files": files_sorted
96
+ }
97
+
98
+ return json.dumps(file_order)
99
+
100
+ my_inputs = [
101
+ gr.ColorPicker(value="#ffdf00", label="center_color", interactive=True)
102
+ ]
103
+
104
+ my_outputs = [
105
+ gr.JSON(show_label=False, show_indices=False, height=200, container=False)
106
+ ]
107
+
108
+ my_examples = [
109
+ ["#FFFFFF"],
110
+ ["#FFD700"],
111
+ ["#7814BE"]
112
+ ]
113
+
114
+ def setup():
115
+ global FILE_COLORS
116
+ FLOWER_IMG_DIR = "./data/image/flowers"
117
+ if not path.isdir(FLOWER_IMG_DIR):
118
+ download_extract()
119
+ FILE_COLORS = get_top_colors(FLOWER_IMG_DIR)
120
+
121
+ setup()
122
+
123
+ with gr.Blocks() as demo:
124
+ gr.Interface(
125
+ fn=order_by_color,
126
+ inputs=my_inputs,
127
+ outputs=my_outputs,
128
+ cache_examples=True,
129
+ examples=my_examples,
130
+ allow_flagging="never",
131
+ fill_width=True
132
+ )
133
+
134
+ if __name__ == "__main__":
135
+ demo.launch()
index.html ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+
4
+ <head>
5
+ <style>
6
+ .container {
7
+ max-width: 768px;
8
+ display: flex;
9
+ flex-direction: row;
10
+ flex-wrap: wrap;
11
+ margin: 0 auto;
12
+ }
13
+
14
+ .color-picker {
15
+ position: sticky;
16
+ top: 20px;
17
+ height: 50px;
18
+ }
19
+
20
+ .img-img {
21
+ width: 128px;
22
+ }
23
+ </style>
24
+
25
+ <script>
26
+ let mClient;
27
+ async function preload() {
28
+ const Gradio = await import("https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js");
29
+ mClient = await Gradio.Client.connect("visualizedata/PSAM5020-ColorSort-Gradio");
30
+ document.getElementById("color-picker").dispatchEvent(new Event('change'));
31
+ }
32
+ preload();
33
+
34
+ const IMGS_URL = "https://raw.githubusercontent.com/PSAM-5020-2025F-A/5020-utils/main/datasets/image/flowers";
35
+
36
+ function orderImages(imageFiles) {
37
+ const container = document.getElementById('main-container');
38
+ container.innerHTML = "";
39
+
40
+ imageFiles.forEach((fname, i) => {
41
+ const imgDivEl = document.createElement("div");
42
+ imgDivEl.classList.add("img-container");
43
+ container.appendChild(imgDivEl);
44
+
45
+ const imgEl = document.createElement("img");
46
+ imgEl.setAttribute("src", `${IMGS_URL}/${fname}`);
47
+ imgEl.classList.add("img-img");
48
+ imgDivEl.appendChild(imgEl);
49
+ });
50
+ }
51
+
52
+ window.addEventListener("load", (event) => {
53
+ const colorEl = document.getElementById("color-picker");
54
+ colorEl.addEventListener("change", async (event) => {
55
+ document.getElementById('main-container').innerHTML = "Loading...";
56
+ const result = await mClient.predict("/predict", {
57
+ center_color_str: event.target.value
58
+ });
59
+ orderImages(result.data[0].files);
60
+ });
61
+ });
62
+ </script>
63
+ </head>
64
+
65
+ <body>
66
+ <input type="color" id="color-picker" class="color-picker" value="#fffffe">
67
+ <div id="main-container" class="container"></div>
68
+ </body>
69
+
70
+ </html>
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ pydantic==2.8.2
2
+ huggingface-hub==0.34.3
3
+ scikit-learn