{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%reload_ext autoreload\n", "%autoreload 2\n", "\n", "import requests\n", "from io import BytesIO\n", "import numpy as np\n", "from PIL import Image\n", "import matplotlib.pyplot as plt\n", "import matplotlib.colors as mcolors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Hues on Color Wheel\n", "\n", "On the color wheel, hues form a circle — not a straight line. Reds live near both 0° and 360° (or near 0 and 1 in normalized format). \n", "\n", "Naive algorithm may mistakenly think there are two separate groups of reds instead of the true one." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "url = \"https://github.com/timpyrkov/circleclust/blob/master/tests/image.jpg?raw=true\"\n", "\n", "# Read image from url (image.jpg from github repository)\n", "response = requests.get(url)\n", "img = Image.open(BytesIO(response.content))\n", "\n", "# Show image\n", "plt.imshow(img)\n", "plt.axis('off')\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Convert to RGB array\n", "if img.mode != 'RGB':\n", " img = img.convert('RGB')\n", "rgb = np.array(img) / 255\n", "\n", "# Convert HSV and extract Hue (H) coordinates on the color wheel\n", "hsv = mcolors.rgb_to_hsv(rgb)\n", "hue = hsv[:, :, 0].flatten()\n", "\n", "# Show histogram of hue values in range [0,1)\n", "plt.figure(figsize=(10, 5))\n", "plt.title('Histogram of pixel hues', fontsize=14)\n", "plt.hist(hue, bins=np.linspace(0, 1, 50), width=.017, alpha=.3)\n", "\n", "plt.xticks([0.0, 0.17, 0.33, 0.5, 0.67, 0.83, 1.0], \n", " [\"Reds\", \"Yellows\", \"Greens\", \"Cyans\", \"Blues\", \"Magentas\", \"Reds\"])\n", "\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Find Dominant Hues\n", " \n", " Detect dominant pixel hues (\"Reds\" and \"Magentas\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from circleclust import CircleClust\n", "\n", "# Initialize and run CircleClust.fit() to find pixel group centroids\n", "cc = CircleClust()\n", "cc.fit(hue, period=1) # Important: provide correct range of data values period!\n", "\n", "# Print detected centroids\n", "cc.get_centroids()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Show detected centroids\n", "cc.show_centroids()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Hue Clusters Size\n", "\n", "Note that the \"Reds\" cluster occupies the area of wrap near Hue = 0. Both pixels close to 0 and close to 1 are attributed to the same single \"Reds\" cluster." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Run CircleClust.predict() to assign cluster labels\n", "clust_ids = cc.predict(hue, width_scale=1.0)\n", "\n", "# Print number of each cluster labels (-1 stands for outliers)\n", "np.unique(clust_ids, return_counts=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Show histogram of each pixel hue cluster\n", "plt.figure(figsize=(10, 5))\n", "plt.title('Histogram of pixel hues per cluster', fontsize=14)\n", "\n", "bins = np.linspace(0, 1, 50)\n", "color = [\"grey\", \"red\", \"magenta\"]\n", "name = [\"Outliers\", \"Reds\", \"Magentas\"]\n", "clist, ccount = np.unique(clust_ids, return_counts=True)\n", "for i, clust_id in enumerate(clist):\n", " mask = clust_ids == clust_id\n", " label = f\"{name[i]}: {ccount[i]:_}\".replace(\"_\", \"'\")\n", " plt.hist(hue[mask], bins=bins, width=.017, alpha=.3, color=color[i], label=label)\n", "\n", "plt.xticks([0.0, 0.17, 0.33, 0.5, 0.67, 0.83, 1.0], \n", " [\"Reds\", \"Yellows\", \"Greens\", \"Cyans\", \"Blues\", \"Magentas\", \"Reds\"])\n", "\n", "plt.legend()\n", "plt.show()\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.11" }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 1 }