diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a6344aa --- /dev/null +++ b/.gitattributes @@ -0,0 +1,35 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ckpt filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.mlmodel filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text +*.npz filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.safetensors filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/deploy-hf.yml b/.github/workflows/deploy-hf.yml new file mode 100644 index 0000000..5fea227 --- /dev/null +++ b/.github/workflows/deploy-hf.yml @@ -0,0 +1,25 @@ +name: Deploy to Hugging Face spaces + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout Dev Repo + uses: actions/checkout@v3 + with: + fetch-depth: 0 + lfs: true + + - name: Push to HF + env: + HFTOKEN: ${{ secrets.HFTOKEN }} + + run: | + git remote add hf https://thiagohersan:$HFTOKEN@huggingface.co/spaces/thiagohersan/predominant-color + git push hf main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d0c2e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_S* +__pycache__/ +gradio_cached_examples/ +.ipynb_checkpoints/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..929f6fb --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +--- +title: Predominant Color Calculator +emoji: 🤨🧮 +colorFrom: blue +colorTo: gray +sdk: gradio +sdk_version: 4.42.0 +app_file: app.py +pinned: false +--- diff --git a/app.py b/app.py new file mode 100644 index 0000000..44e328d --- /dev/null +++ b/app.py @@ -0,0 +1,35 @@ +import gradio as gr +from PIL import Image as PImage + +from dominant_color import get_dominant_colors, rgb255_to_hex_str + +NUM_OUTS = 4 +all_outputs = [gr.ColorPicker(visible=False) for _ in range(NUM_OUTS)] + +def dom_col(img_in): + rgb_by_cnt, rgb_by_hls = get_dominant_colors(img_in) + palette_cnt = [[int(v) for v in c] for c in rgb_by_cnt[:NUM_OUTS]] + palette_hls = [[int(v) for v in c] for c in rgb_by_hls[:NUM_OUTS]] + return palette_cnt, palette_hls + +def dom_col_hls(img_in): + _, palette_hls = dom_col(img_in) + palette_hex = [rgb255_to_hex_str(c) for c in palette_hls] + return [gr.ColorPicker(h, label=f"{h}", show_label=True, visible=True) for h in palette_hex] + +with gr.Blocks() as demo: + gr.Markdown(""" + # Dominant color calculator + """) + + gr.Interface( + dom_col_hls, + inputs=gr.Image(type="pil"), + outputs=all_outputs, + cache_examples=True, + examples=[["./imgs/03.webp"], ["./imgs/11.jpg"]], + allow_flagging="never", + ) + +if __name__ == "__main__": + demo.launch() diff --git a/dominant_color.py b/dominant_color.py new file mode 100644 index 0000000..6664cb2 --- /dev/null +++ b/dominant_color.py @@ -0,0 +1,60 @@ +import cv2 + +import numpy as np + +from colorsys import hls_to_rgb, rgb_to_hls + +# CRITERIA: MAX_ITER, EPSILON_ACCURACY +# cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS + +CV_KMEANS_PARAMS = { + "K": 8, + "bestLabels": None, + "criteria": (cv2.TERM_CRITERIA_EPS, -1, 0.02), + "attempts": 8, + "flags": cv2.KMEANS_RANDOM_CENTERS +} + +def resize_PIL(pimg, max_dim=256): + iw, ih = pimg.size + resize_ratio = min(max_dim / iw, max_dim / ih) + new_size = (int(iw * resize_ratio), int(ih * resize_ratio)) + return pimg.resize(new_size) + +def rgb01_to_rgb255(c): + return tuple(np.multiply(c, 255).astype(np.uint8)) + +def rgb255_to_rgb01(c): + return tuple(np.multiply(c, 1.0 / 255.0).astype(np.float32)) + +def hls_to_rgb255(c): + return rgb01_to_rgb255(hls_to_rgb(*c)) + +def rgb255_to_hex_str(c): + r, g, b = c + return "#{:02x}{:02x}{:02x}".format(r, g, b) + +def hls_order(c): + _,l,s = c + l_term = 2 * abs(l - 0.5) + return l_term + (1.0 - s) if l_term < 0.5 else 1.5 * l_term + +def hls_order_from_rgb255(c): + return hls_order(rgb_to_hls(*rgb255_to_rgb01(c))) + +def get_dominant_colors(pimg, max_dim=256): + rpimg = resize_PIL(pimg, max_dim) + np_img = np.array(rpimg).astype(np.float32) + _, labels, centers = cv2.kmeans(np_img.reshape(-1, 3), **CV_KMEANS_PARAMS) + centers = centers.astype(np.uint8) + + _, counts = np.unique(labels, return_counts=True) + by_count = np.argsort(-counts) + + centers_hls = [rgb_to_hls(*rgb255_to_rgb01(c)) for c in centers] + by_hls = sorted(centers_hls, key=hls_order) + + rgb_by_count = [tuple(centers[c]) for c in by_count] + rgb_by_hls = [hls_to_rgb255(c) for c in by_hls] + + return rgb_by_count, rgb_by_hls diff --git a/imgs/03.webp b/imgs/03.webp new file mode 100644 index 0000000..f5eb366 Binary files /dev/null and b/imgs/03.webp differ diff --git a/imgs/11.jpg b/imgs/11.jpg new file mode 100644 index 0000000..c822861 Binary files /dev/null and b/imgs/11.jpg differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0efc7c8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +fastapi==0.112.2 +opencv-python