version k-means qui marche!!

This commit is contained in:
2025-11-21 14:31:37 +01:00
parent b77383681e
commit 1ff888cf34
43 changed files with 431 additions and 3178 deletions

View File

@@ -1,2 +0,0 @@
__pycache__
.venv

View File

@@ -1,12 +0,0 @@
# Code snippets
This repo contains the code snippets I reference in [my blog](https://boonepeter.github.io/).
## Index
Code|Blog Post|
---|---|
[spotify-codes](./spotify-codes)|[How do Spotify Codes Work?](https://boonepeter.github.io/posts/2020-11-10-spotify-codes/)|
[spotify-codes-part-2](./spotify-codes-part-2)|[Spotify Codes Part 2](https://boonepeter.github.io/posts/spotify-codes-part-2/)
[snapcodes](./snapcodes)|No post yet

View File

@@ -1,37 +0,0 @@
# Snapcodes
Using Python 3.10.1
```bash
pip3 install -r requirements.txt
```
Running this command
```bash
python3 img_to_points.py snapcode.png
```
Gets the points from this image:
![](./src/snapcode.png)
Points:
```python
[1, 2, 3, 4, 6, 9, 14, 18, 20, 21, 22, 23, 27, 28, 29, 30, 32, 38, 39, 41, 43, 44, 45, 49, 53, 58, 59, 62, 64, 65, 66, 67, 70, 71, 73, 76, 77, 83, 87, 90, 93, 94, 96, 99, 100, 101, 103, 104, 105, 106, 107, 108, 109, 110, 112, 113, 115, 118, 119, 120, 130, 131, 134, 140, 141, 144, 145, 146, 150, 153, 155, 157, 159, 161, 163, 169, 170, 171, 172, 173, 174, 175, 178, 182, 183, 184, 185, 188, 190, 192, 193, 196, 197, 202, 204, 206, 207, 209]
```
Points are numbered left to right, top to bottom, starting with 0 and ending at 211.
And this command
```bash
python3 img_to_svg.py -i snapcode.png -o output.svg
```
Outputs an SVG file:
![](./src/output.svg)
The svg is useful for checking that the point parsing was correct.

View File

@@ -1,10 +0,0 @@
imageio==2.13.5
networkx==2.6.3
numpy==1.22.1
packaging==21.3
Pillow==9.0.0
pyparsing==3.0.6
PyWavelets==1.2.0
scikit-image==0.19.1
scipy==1.7.3
tifffile==2021.11.2

View File

@@ -1,2 +0,0 @@
/imgs
.ipynb_checkpoints/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

View File

@@ -1,305 +0,0 @@
import argparse
from skimage import io
import numpy as np
from skimage import io, img_as_ubyte
from skimage.filters import threshold_otsu
from skimage.color import rgb2gray, rgba2rgb
from skimage.measure import label, regionprops
## BBOX of points from all_dots_yellow.png
ALL_BBOX = [
[0.025, 0.076, 0.0583, 0.1094],
[0.025, 0.1271, 0.0583, 0.1604],
[0.025, 0.1781, 0.0583, 0.2115],
[0.025, 0.2292, 0.0583, 0.2625],
[0.025, 0.2802, 0.0583, 0.3135],
[0.025, 0.3302, 0.0583, 0.3635],
[0.025, 0.3812, 0.0583, 0.4146],
[0.025, 0.4323, 0.0583, 0.4656],
[0.025, 0.4833, 0.0583, 0.5167],
[0.025, 0.5344, 0.0583, 0.5677],
[0.025, 0.5854, 0.0583, 0.6188],
[0.025, 0.6365, 0.0583, 0.6698],
[0.025, 0.6865, 0.0583, 0.7198],
[0.025, 0.7375, 0.0583, 0.7708],
[0.025, 0.7885, 0.0583, 0.8219],
[0.025, 0.8396, 0.0583, 0.8729],
[0.025, 0.8906, 0.0583, 0.924],
[0.076, 0.025, 0.1094, 0.0583],
[0.076, 0.076, 0.1094, 0.1094],
[0.076, 0.1271, 0.1094, 0.1604],
[0.076, 0.1781, 0.1094, 0.2115],
[0.076, 0.2292, 0.1094, 0.2625],
[0.076, 0.2802, 0.1094, 0.3135],
[0.076, 0.3302, 0.1094, 0.3635],
[0.076, 0.3812, 0.1094, 0.4146],
[0.076, 0.4323, 0.1094, 0.4656],
[0.076, 0.4833, 0.1094, 0.5167],
[0.076, 0.5344, 0.1094, 0.5677],
[0.076, 0.5854, 0.1094, 0.6188],
[0.076, 0.6365, 0.1094, 0.6698],
[0.076, 0.6865, 0.1094, 0.7198],
[0.076, 0.7375, 0.1094, 0.7708],
[0.076, 0.7885, 0.1094, 0.8219],
[0.076, 0.8396, 0.1094, 0.8729],
[0.076, 0.8906, 0.1094, 0.924],
[0.076, 0.9417, 0.1094, 0.975],
[0.1271, 0.025, 0.1604, 0.0583],
[0.1271, 0.076, 0.1604, 0.1094],
[0.1271, 0.1271, 0.1604, 0.1604],
[0.1271, 0.1781, 0.1604, 0.2115],
[0.1271, 0.2292, 0.1604, 0.2625],
[0.1271, 0.2802, 0.1604, 0.3135],
[0.1271, 0.3312, 0.1604, 0.3635],
[0.1271, 0.3812, 0.1604, 0.4146],
[0.1271, 0.4323, 0.1604, 0.4656],
[0.1271, 0.4833, 0.1604, 0.5167],
[0.1271, 0.5344, 0.1604, 0.5677],
[0.1271, 0.5854, 0.1604, 0.6188],
[0.1271, 0.6365, 0.1604, 0.6698],
[0.1271, 0.6865, 0.1604, 0.7198],
[0.1271, 0.7375, 0.1604, 0.7708],
[0.1271, 0.7885, 0.1604, 0.8219],
[0.1271, 0.8396, 0.1604, 0.8729],
[0.1271, 0.8906, 0.1604, 0.924],
[0.1271, 0.9417, 0.1604, 0.975],
[0.1781, 0.025, 0.2115, 0.0583],
[0.1781, 0.076, 0.2115, 0.1094],
[0.1781, 0.1271, 0.2115, 0.1604],
[0.1781, 0.1781, 0.2115, 0.2115],
[0.1781, 0.2292, 0.2115, 0.2625],
[0.1781, 0.2802, 0.2115, 0.3135],
[0.1781, 0.6865, 0.2115, 0.7198],
[0.1781, 0.7375, 0.2115, 0.7708],
[0.1781, 0.7885, 0.2115, 0.8219],
[0.1781, 0.8396, 0.2115, 0.8729],
[0.1781, 0.8906, 0.2115, 0.924],
[0.1781, 0.9417, 0.2115, 0.975],
[0.2292, 0.025, 0.2625, 0.0583],
[0.2292, 0.076, 0.2625, 0.1094],
[0.2292, 0.1271, 0.2625, 0.1604],
[0.2292, 0.1781, 0.2625, 0.2115],
[0.2292, 0.2292, 0.2625, 0.2625],
[0.2292, 0.7375, 0.2625, 0.7708],
[0.2292, 0.7885, 0.2625, 0.8219],
[0.2292, 0.8396, 0.2625, 0.8729],
[0.2292, 0.8906, 0.2625, 0.924],
[0.2292, 0.9417, 0.2625, 0.975],
[0.2802, 0.025, 0.3135, 0.0583],
[0.2802, 0.076, 0.3135, 0.1094],
[0.2802, 0.1271, 0.3135, 0.1604],
[0.2802, 0.1781, 0.3135, 0.2115],
[0.2802, 0.7885, 0.3135, 0.8219],
[0.2802, 0.8396, 0.3135, 0.8729],
[0.2802, 0.8906, 0.3135, 0.924],
[0.2802, 0.9417, 0.3135, 0.975],
[0.3302, 0.025, 0.3635, 0.0583],
[0.3302, 0.076, 0.3635, 0.1094],
[0.3302, 0.1271, 0.3635, 0.1604],
[0.3302, 0.8396, 0.3635, 0.8729],
[0.3302, 0.8906, 0.3635, 0.924],
[0.3302, 0.9417, 0.3635, 0.975],
[0.3812, 0.025, 0.4146, 0.0583],
[0.3812, 0.076, 0.4146, 0.1094],
[0.3812, 0.1271, 0.4146, 0.1604],
[0.3812, 0.8396, 0.4146, 0.8729],
[0.3812, 0.8906, 0.4146, 0.924],
[0.3812, 0.9417, 0.4146, 0.975],
[0.4323, 0.025, 0.4656, 0.0583],
[0.4323, 0.076, 0.4656, 0.1094],
[0.4323, 0.1271, 0.4656, 0.1604],
[0.4323, 0.8396, 0.4656, 0.8729],
[0.4323, 0.8906, 0.4656, 0.924],
[0.4323, 0.9417, 0.4656, 0.975],
[0.4833, 0.025, 0.5167, 0.0583],
[0.4833, 0.076, 0.5167, 0.1094],
[0.4833, 0.1271, 0.5167, 0.1604],
[0.4833, 0.8396, 0.5167, 0.8729],
[0.4833, 0.8906, 0.5167, 0.924],
[0.4833, 0.9417, 0.5167, 0.975],
[0.5344, 0.025, 0.5677, 0.0583],
[0.5344, 0.076, 0.5677, 0.1094],
[0.5344, 0.1271, 0.5677, 0.1604],
[0.5344, 0.8396, 0.5677, 0.8729],
[0.5344, 0.8906, 0.5677, 0.924],
[0.5344, 0.9417, 0.5677, 0.975],
[0.5854, 0.025, 0.6188, 0.0583],
[0.5854, 0.076, 0.6188, 0.1094],
[0.5854, 0.1271, 0.6188, 0.1604],
[0.5854, 0.8396, 0.6188, 0.8729],
[0.5854, 0.8906, 0.6188, 0.924],
[0.5854, 0.9417, 0.6188, 0.975],
[0.6365, 0.025, 0.6698, 0.0583],
[0.6365, 0.076, 0.6698, 0.1094],
[0.6365, 0.1271, 0.6698, 0.1604],
[0.6365, 0.8396, 0.6698, 0.8729],
[0.6365, 0.8906, 0.6698, 0.924],
[0.6365, 0.9417, 0.6698, 0.975],
[0.6865, 0.025, 0.7198, 0.0583],
[0.6865, 0.076, 0.7198, 0.1094],
[0.6865, 0.1271, 0.7198, 0.1604],
[0.6865, 0.1781, 0.7198, 0.2115],
[0.6865, 0.7885, 0.7198, 0.8219],
[0.6865, 0.8396, 0.7198, 0.8729],
[0.6865, 0.8906, 0.7198, 0.924],
[0.6865, 0.9417, 0.7198, 0.975],
[0.7375, 0.025, 0.7708, 0.0583],
[0.7375, 0.076, 0.7708, 0.1094],
[0.7375, 0.1271, 0.7708, 0.1604],
[0.7375, 0.1781, 0.7708, 0.2115],
[0.7375, 0.2292, 0.7708, 0.2625],
[0.7375, 0.7375, 0.7708, 0.7708],
[0.7375, 0.7885, 0.7708, 0.8219],
[0.7375, 0.8396, 0.7708, 0.8729],
[0.7375, 0.8906, 0.7708, 0.924],
[0.7375, 0.9417, 0.7708, 0.975],
[0.7885, 0.025, 0.8219, 0.0583],
[0.7885, 0.076, 0.8219, 0.1094],
[0.7885, 0.1271, 0.8219, 0.1604],
[0.7885, 0.1781, 0.8219, 0.2115],
[0.7885, 0.2292, 0.8219, 0.2625],
[0.7885, 0.2802, 0.8219, 0.3135],
[0.7885, 0.6865, 0.8219, 0.7198],
[0.7885, 0.7375, 0.8219, 0.7708],
[0.7885, 0.7885, 0.8219, 0.8219],
[0.7885, 0.8396, 0.8219, 0.8729],
[0.7885, 0.8906, 0.8219, 0.924],
[0.7885, 0.9417, 0.8219, 0.975],
[0.8396, 0.025, 0.8729, 0.0583],
[0.8396, 0.076, 0.8729, 0.1094],
[0.8396, 0.1271, 0.8729, 0.1604],
[0.8396, 0.1781, 0.8729, 0.2115],
[0.8396, 0.2292, 0.8729, 0.2625],
[0.8396, 0.2802, 0.8729, 0.3135],
[0.8396, 0.3312, 0.8729, 0.3635],
[0.8396, 0.3812, 0.8729, 0.4146],
[0.8396, 0.4323, 0.8729, 0.4656],
[0.8396, 0.4833, 0.8729, 0.5167],
[0.8396, 0.5344, 0.8729, 0.5677],
[0.8396, 0.5854, 0.8729, 0.6188],
[0.8396, 0.6365, 0.8729, 0.6698],
[0.8396, 0.6865, 0.8729, 0.7198],
[0.8396, 0.7375, 0.8729, 0.7708],
[0.8396, 0.7885, 0.8729, 0.8219],
[0.8396, 0.8396, 0.8729, 0.8729],
[0.8396, 0.8906, 0.8729, 0.924],
[0.8396, 0.9417, 0.8729, 0.975],
[0.8906, 0.025, 0.924, 0.0583],
[0.8906, 0.076, 0.924, 0.1094],
[0.8906, 0.1271, 0.924, 0.1604],
[0.8906, 0.1781, 0.924, 0.2115],
[0.8906, 0.2292, 0.924, 0.2625],
[0.8906, 0.2802, 0.924, 0.3135],
[0.8906, 0.3302, 0.924, 0.3635],
[0.8906, 0.3812, 0.924, 0.4146],
[0.8906, 0.4323, 0.924, 0.4656],
[0.8906, 0.4833, 0.924, 0.5167],
[0.8906, 0.5344, 0.924, 0.5677],
[0.8906, 0.5854, 0.924, 0.6188],
[0.8906, 0.6365, 0.924, 0.6698],
[0.8906, 0.6865, 0.924, 0.7198],
[0.8906, 0.7375, 0.924, 0.7708],
[0.8906, 0.7885, 0.924, 0.8219],
[0.8906, 0.8396, 0.924, 0.8729],
[0.8906, 0.8906, 0.924, 0.924],
[0.8906, 0.9417, 0.924, 0.975],
[0.9417, 0.076, 0.975, 0.1094],
[0.9417, 0.1271, 0.975, 0.1604],
[0.9417, 0.1781, 0.975, 0.2115],
[0.9417, 0.2292, 0.975, 0.2625],
[0.9417, 0.2802, 0.975, 0.3135],
[0.9417, 0.3302, 0.975, 0.3635],
[0.9417, 0.3812, 0.975, 0.4146],
[0.9417, 0.4323, 0.975, 0.4656],
[0.9417, 0.4833, 0.975, 0.5167],
[0.9417, 0.5344, 0.975, 0.5677],
[0.9417, 0.5854, 0.975, 0.6188],
[0.9417, 0.6365, 0.975, 0.6698],
[0.9417, 0.6865, 0.975, 0.7198],
[0.9417, 0.7375, 0.975, 0.7708],
[0.9417, 0.7885, 0.975, 0.8219],
[0.9417, 0.8396, 0.975, 0.8729],
[0.9417, 0.8906, 0.975, 0.924],
]
def color_crop(im, color=[(250, 255), (245, 255), (0, 10)]):
"""Crop an image to the min and max occurances of a color range.
Accepts an image and list of colors (channels and colors must match).
"""
assert len(color) == im.shape[-1]
binary = np.ones(im.shape[:-1], dtype=bool)
for i in range(3):
binary &= (im[:,:,i] >= color[i][0]) & (im[:,:,i] <= color[i][1])
labeled = label(binary, background=0)
rp = regionprops(labeled)
bbox = max(rp, key=lambda x: x.bbox_area).bbox
return im[bbox[0]:bbox[2], bbox[1]:bbox[3], :]
def _get_points(image):
im = rgb2gray(image)
t = threshold_otsu(im)
return im > t
def get_bbox_and_centers(im, color=[(250, 255), (245, 255), (0, 10)]):
im = color_crop(im, color=color)
p = _get_points(im)
l = label(p, background=1)
rp = regionprops(l)
(maxx, maxy) = l.shape
max_size = l.shape[0] * l.shape[1] // 500
min_size = l.shape[0] * l.shape[1] // 2000
bbs = []
centers = []
for props in rp:
minr, minc, maxr, maxc = props.bbox
if props.bbox_area < max_size and props.bbox_area > min_size:
bbs.append((props.bbox[0] / maxx, props.bbox[1] / maxx, props.bbox[2] / maxx, props.bbox[3] / maxx))
centers.append(
(
(((minr / maxy) + (maxr / maxy)) / 2),
(((minc / maxx) + (maxc / maxx)) / 2)
),
)
return bbs, centers
def get_numbers(centers, bbs):
points = []
for i, b in enumerate(bbs):
for x, y in centers:
if x > b[0] and x < b[2] and y > b[1] and y < b[3]:
points.append(i)
return points
def img_to_points(filepath, check_counts=False):
im = io.imread(filepath)
color = [(250, 255), (245, 255), (0, 10)]
# convert to 3 channel
if im.shape[-1] == 4:
im = rgba2rgb(im)
# convert to uint8 (0-255)
im = img_as_ubyte(im)
try:
_, centers = get_bbox_and_centers(im, color)
except:
try:
# for some reason, some of the colors are off
# when the images are read in
color = [(250, 255), (245, 255), (75, 90)]
_, centers = get_bbox_and_centers(im, color)
except Exception as e:
raise e
if check_counts and (len(centers) < 50 or len(centers) > 120):
raise Exception("Too many or too few points")
return get_numbers(centers, ALL_BBOX)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("filepath", type=str)
parser.add_argument("--check_counts", action="store_true")
args = parser.parse_args()
print(img_to_points(args.filepath, args.check_counts))

View File

@@ -1,21 +0,0 @@
import argparse
from img_to_points import img_to_points
from points_to_svg import points_to_svg
def img_to_svg(filepath, output_filepath):
"""
Converts an image to an SVG file.
"""
points = img_to_points(filepath, True)
print(points)
svg = points_to_svg(points)
with open(output_filepath, "w") as f:
f.write(svg)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Convert an image to an SVG file.")
parser.add_argument("-i", "--input", help="Input filepath", required=True)
parser.add_argument("-o", "--output", help="Output filepath", required=True)
args = parser.parse_args()
img_to_svg(args.input, args.output)

View File

@@ -1,300 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" height="1024" version="1.1" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg">
<path d=" M749.88,641.75 C763.31,649.46,754.85,644.24,749.88,641.75 M0,860.16 C0,950.64,73.36,1024,163.84,1024 L860.16,1024 C950.64,1024,1024,950.64,1024,860.16 L1024,163.84 C1024,73.36,950.64,0,860.16,0 L163.84,0 C73.36,0,0,73.36,0,163.84 L0,860.16" fill="#000" fill-rule="evenodd"/>
<path d="M32,163.84 C32,91.03,91.03,32,163.84,32 L860.16,32 C932.97,32,992,91.03,992,163.84 L992,860.16 C992,932.97,932.97,992,860.16,992 L163.84,992 C91.03,992,32,932.97,32,860.16 L32,163.84
M170.10500000000002,56.42
A16,16,0,0,0,170.10500000000002,88.42
A16,16,0,0,0,170.10500000000002,56.42
M218.9475,56.42
A16,16,0,0,0,218.9475,88.42
A16,16,0,0,0,218.9475,56.42
M267.79,56.42
A16,16,0,0,0,267.79,88.42
A16,16,0,0,0,267.79,56.42
M316.6325,56.42
A16,16,0,0,0,316.6325,88.42
A16,16,0,0,0,316.6325,56.42
M414.31750000000005,56.42
A16,16,0,0,0,414.31750000000005,88.42
A16,16,0,0,0,414.31750000000005,56.42
M560.845,56.42
A16,16,0,0,0,560.845,88.42
A16,16,0,0,0,560.845,56.42
M805.0575,56.42
A16,16,0,0,0,805.0575,88.42
A16,16,0,0,0,805.0575,56.42
M121.2625,105.2625
A16,16,0,0,0,121.2625,137.2625
A16,16,0,0,0,121.2625,105.2625
M218.9475,105.2625
A16,16,0,0,0,218.9475,137.2625
A16,16,0,0,0,218.9475,105.2625
M267.79,105.2625
A16,16,0,0,0,267.79,137.2625
A16,16,0,0,0,267.79,105.2625
M316.6325,105.2625
A16,16,0,0,0,316.6325,137.2625
A16,16,0,0,0,316.6325,105.2625
M365.475,105.2625
A16,16,0,0,0,365.475,137.2625
A16,16,0,0,0,365.475,105.2625
M560.845,105.2625
A16,16,0,0,0,560.845,137.2625
A16,16,0,0,0,560.845,105.2625
M609.6875,105.2625
A16,16,0,0,0,609.6875,137.2625
A16,16,0,0,0,609.6875,105.2625
M658.53,105.2625
A16,16,0,0,0,658.53,137.2625
A16,16,0,0,0,658.53,105.2625
M707.3725,105.2625
A16,16,0,0,0,707.3725,137.2625
A16,16,0,0,0,707.3725,105.2625
M805.0575,105.2625
A16,16,0,0,0,805.0575,137.2625
A16,16,0,0,0,805.0575,105.2625
M170.10500000000002,154.10500000000002
A16,16,0,0,0,170.10500000000002,186.10500000000002
A16,16,0,0,0,170.10500000000002,154.10500000000002
M218.9475,154.10500000000002
A16,16,0,0,0,218.9475,186.10500000000002
A16,16,0,0,0,218.9475,154.10500000000002
M316.6325,154.10500000000002
A16,16,0,0,0,316.6325,186.10500000000002
A16,16,0,0,0,316.6325,154.10500000000002
M414.31750000000005,154.10500000000002
A16,16,0,0,0,414.31750000000005,186.10500000000002
A16,16,0,0,0,414.31750000000005,154.10500000000002
M463.16,154.10500000000002
A16,16,0,0,0,463.16,186.10500000000002
A16,16,0,0,0,463.16,154.10500000000002
M512.0024999999999,154.10500000000002
A16,16,0,0,0,512.0024999999999,186.10500000000002
A16,16,0,0,0,512.0024999999999,154.10500000000002
M707.3725,154.10500000000002
A16,16,0,0,0,707.3725,186.10500000000002
A16,16,0,0,0,707.3725,154.10500000000002
M902.7425,154.10500000000002
A16,16,0,0,0,902.7425,186.10500000000002
A16,16,0,0,0,902.7425,154.10500000000002
M218.9475,202.9475
A16,16,0,0,0,218.9475,234.9475
A16,16,0,0,0,218.9475,202.9475
M267.79,202.9475
A16,16,0,0,0,267.79,234.9475
A16,16,0,0,0,267.79,202.9475
M756.215,202.9475
A16,16,0,0,0,756.215,234.9475
A16,16,0,0,0,756.215,202.9475
M853.9,202.9475
A16,16,0,0,0,853.9,234.9475
A16,16,0,0,0,853.9,202.9475
M902.7425,202.9475
A16,16,0,0,0,902.7425,234.9475
A16,16,0,0,0,902.7425,202.9475
M951.5849999999999,202.9475
A16,16,0,0,0,951.5849999999999,234.9475
A16,16,0,0,0,951.5849999999999,202.9475
M72.42,251.79000000000002
A16,16,0,0,0,72.42,283.79
A16,16,0,0,0,72.42,251.79000000000002
M218.9475,251.79000000000002
A16,16,0,0,0,218.9475,283.79
A16,16,0,0,0,218.9475,251.79000000000002
M267.79,251.79000000000002
A16,16,0,0,0,267.79,283.79
A16,16,0,0,0,267.79,251.79000000000002
M805.0575,251.79000000000002
A16,16,0,0,0,805.0575,283.79
A16,16,0,0,0,805.0575,251.79000000000002
M951.5849999999999,251.79000000000002
A16,16,0,0,0,951.5849999999999,283.79
A16,16,0,0,0,951.5849999999999,251.79000000000002
M72.42,300.6325
A16,16,0,0,0,72.42,332.6325
A16,16,0,0,0,72.42,300.6325
M902.7425,300.6325
A16,16,0,0,0,902.7425,332.6325
A16,16,0,0,0,902.7425,300.6325
M170.10500000000002,349.475
A16,16,0,0,0,170.10500000000002,381.475
A16,16,0,0,0,170.10500000000002,349.475
M951.5849999999999,349.475
A16,16,0,0,0,951.5849999999999,381.475
A16,16,0,0,0,951.5849999999999,349.475
M170.10500000000002,398.31750000000005
A16,16,0,0,0,170.10500000000002,430.31750000000005
A16,16,0,0,0,170.10500000000002,398.31750000000005
M853.9,398.31750000000005
A16,16,0,0,0,853.9,430.31750000000005
A16,16,0,0,0,853.9,398.31750000000005
M951.5849999999999,398.31750000000005
A16,16,0,0,0,951.5849999999999,430.31750000000005
A16,16,0,0,0,951.5849999999999,398.31750000000005
M170.10500000000002,447.16
A16,16,0,0,0,170.10500000000002,479.16
A16,16,0,0,0,170.10500000000002,447.16
M853.9,447.16
A16,16,0,0,0,853.9,479.16
A16,16,0,0,0,853.9,447.16
M902.7425,447.16
A16,16,0,0,0,902.7425,479.16
A16,16,0,0,0,902.7425,447.16
M72.42,496.0025
A16,16,0,0,0,72.42,528.0025
A16,16,0,0,0,72.42,496.0025
M121.2625,496.0025
A16,16,0,0,0,121.2625,528.0025
A16,16,0,0,0,121.2625,496.0025
M170.10500000000002,496.0025
A16,16,0,0,0,170.10500000000002,528.0025
A16,16,0,0,0,170.10500000000002,496.0025
M853.9,496.0025
A16,16,0,0,0,853.9,528.0025
A16,16,0,0,0,853.9,496.0025
M902.7425,496.0025
A16,16,0,0,0,902.7425,528.0025
A16,16,0,0,0,902.7425,496.0025
M951.5849999999999,496.0025
A16,16,0,0,0,951.5849999999999,528.0025
A16,16,0,0,0,951.5849999999999,496.0025
M72.42,544.845
A16,16,0,0,0,72.42,576.845
A16,16,0,0,0,72.42,544.845
M121.2625,544.845
A16,16,0,0,0,121.2625,576.845
A16,16,0,0,0,121.2625,544.845
M853.9,544.845
A16,16,0,0,0,853.9,576.845
A16,16,0,0,0,853.9,544.845
M902.7425,544.845
A16,16,0,0,0,902.7425,576.845
A16,16,0,0,0,902.7425,544.845
M72.42,593.6875
A16,16,0,0,0,72.42,625.6875
A16,16,0,0,0,72.42,593.6875
M853.9,593.6875
A16,16,0,0,0,853.9,625.6875
A16,16,0,0,0,853.9,593.6875
M902.7425,593.6875
A16,16,0,0,0,902.7425,625.6875
A16,16,0,0,0,902.7425,593.6875
M951.5849999999999,593.6875
A16,16,0,0,0,951.5849999999999,625.6875
A16,16,0,0,0,951.5849999999999,593.6875
M218.9475,691.3725
A16,16,0,0,0,218.9475,723.3725
A16,16,0,0,0,218.9475,691.3725
M805.0575,691.3725
A16,16,0,0,0,805.0575,723.3725
A16,16,0,0,0,805.0575,691.3725
M951.5849999999999,691.3725
A16,16,0,0,0,951.5849999999999,723.3725
A16,16,0,0,0,951.5849999999999,691.3725
M756.215,740.215
A16,16,0,0,0,756.215,772.215
A16,16,0,0,0,756.215,740.215
M805.0575,740.215
A16,16,0,0,0,805.0575,772.215
A16,16,0,0,0,805.0575,740.215
M951.5849999999999,740.215
A16,16,0,0,0,951.5849999999999,772.215
A16,16,0,0,0,951.5849999999999,740.215
M72.42,789.0575
A16,16,0,0,0,72.42,821.0575
A16,16,0,0,0,72.42,789.0575
M121.2625,789.0575
A16,16,0,0,0,121.2625,821.0575
A16,16,0,0,0,121.2625,789.0575
M316.6325,789.0575
A16,16,0,0,0,316.6325,821.0575
A16,16,0,0,0,316.6325,789.0575
M805.0575,789.0575
A16,16,0,0,0,805.0575,821.0575
A16,16,0,0,0,805.0575,789.0575
M902.7425,789.0575
A16,16,0,0,0,902.7425,821.0575
A16,16,0,0,0,902.7425,789.0575
M72.42,837.9
A16,16,0,0,0,72.42,869.9
A16,16,0,0,0,72.42,837.9
M170.10500000000002,837.9
A16,16,0,0,0,170.10500000000002,869.9
A16,16,0,0,0,170.10500000000002,837.9
M267.79,837.9
A16,16,0,0,0,267.79,869.9
A16,16,0,0,0,267.79,837.9
M365.475,837.9
A16,16,0,0,0,365.475,869.9
A16,16,0,0,0,365.475,837.9
M658.53,837.9
A16,16,0,0,0,658.53,869.9
A16,16,0,0,0,658.53,837.9
M707.3725,837.9
A16,16,0,0,0,707.3725,869.9
A16,16,0,0,0,707.3725,837.9
M756.215,837.9
A16,16,0,0,0,756.215,869.9
A16,16,0,0,0,756.215,837.9
M805.0575,837.9
A16,16,0,0,0,805.0575,869.9
A16,16,0,0,0,805.0575,837.9
M853.9,837.9
A16,16,0,0,0,853.9,869.9
A16,16,0,0,0,853.9,837.9
M902.7425,837.9
A16,16,0,0,0,902.7425,869.9
A16,16,0,0,0,902.7425,837.9
M951.5849999999999,837.9
A16,16,0,0,0,951.5849999999999,869.9
A16,16,0,0,0,951.5849999999999,837.9
M170.10500000000002,886.7425
A16,16,0,0,0,170.10500000000002,918.7425
A16,16,0,0,0,170.10500000000002,886.7425
M365.475,886.7425
A16,16,0,0,0,365.475,918.7425
A16,16,0,0,0,365.475,886.7425
M414.31750000000005,886.7425
A16,16,0,0,0,414.31750000000005,918.7425
A16,16,0,0,0,414.31750000000005,886.7425
M463.16,886.7425
A16,16,0,0,0,463.16,918.7425
A16,16,0,0,0,463.16,886.7425
M512.0024999999999,886.7425
A16,16,0,0,0,512.0024999999999,918.7425
A16,16,0,0,0,512.0024999999999,886.7425
M658.53,886.7425
A16,16,0,0,0,658.53,918.7425
A16,16,0,0,0,658.53,886.7425
M756.215,886.7425
A16,16,0,0,0,756.215,918.7425
A16,16,0,0,0,756.215,886.7425
M853.9,886.7425
A16,16,0,0,0,853.9,918.7425
A16,16,0,0,0,853.9,886.7425
M902.7425,886.7425
A16,16,0,0,0,902.7425,918.7425
A16,16,0,0,0,902.7425,886.7425
M170.10500000000002,935.5849999999999
A16,16,0,0,0,170.10500000000002,967.5849999999999
A16,16,0,0,0,170.10500000000002,935.5849999999999
M218.9475,935.5849999999999
A16,16,0,0,0,218.9475,967.5849999999999
A16,16,0,0,0,218.9475,935.5849999999999
M463.16,935.5849999999999
A16,16,0,0,0,463.16,967.5849999999999
A16,16,0,0,0,463.16,935.5849999999999
M560.845,935.5849999999999
A16,16,0,0,0,560.845,967.5849999999999
A16,16,0,0,0,560.845,935.5849999999999
M658.53,935.5849999999999
A16,16,0,0,0,658.53,967.5849999999999
A16,16,0,0,0,658.53,935.5849999999999
M707.3725,935.5849999999999
A16,16,0,0,0,707.3725,967.5849999999999
A16,16,0,0,0,707.3725,935.5849999999999
M805.0575,935.5849999999999
A16,16,0,0,0,805.0575,967.5849999999999
A16,16,0,0,0,805.0575,935.5849999999999
" fill="#FFFC00"/>
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,78 +0,0 @@
import argparse
positions = [
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 15, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 16, 17, 18],
[0, 1, 2, 3, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18],
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17],
]
BORDER = 56.42
BORDER_C = 72.42
MULT = 48.8425
DIAMETER = 32
SVG = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" height="1024" version="1.1" viewBox="0 0 1024 1024" width="1024" xmlns="http://www.w3.org/2000/svg">
<path d=" M749.88,641.75 C763.31,649.46,754.85,644.24,749.88,641.75 M0,860.16 C0,950.64,73.36,1024,163.84,1024 L860.16,1024 C950.64,1024,1024,950.64,1024,860.16 L1024,163.84 C1024,73.36,950.64,0,860.16,0 L163.84,0 C73.36,0,0,73.36,0,163.84 L0,860.16" fill="#000" fill-rule="evenodd"/>
<path d="M32,163.84 C32,91.03,91.03,32,163.84,32 L860.16,32 C932.97,32,992,91.03,992,163.84 L992,860.16 C992,932.97,932.97,992,860.16,992 L163.84,992 C91.03,992,32,932.97,32,860.16 L32,163.84
{path}" fill="#FFFC00"/>
</svg>
'''
def points_to_svg(points):
counter = 0
path = ""
points.sort()
for r in range(len(positions)):
for c in range(len(positions[r])):
col = positions[r][c]
if len(points) == 0:
break
if points[0] == counter:
path += f"""M{col * MULT + BORDER_C},{r * MULT + BORDER}
A16,16,0,0,0,{col * MULT + BORDER_C},{r * MULT + BORDER + DIAMETER}
A16,16,0,0,0,{col * MULT + BORDER_C},{r * MULT + BORDER}
"""
points.pop(0)
counter += 1
return SVG.format(path=path)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--points", type=str, help="Points to draw")
parser.add_argument("--output", type=str, help="Output file")
parser.add_argument("--show", action="store_true", help="Show SVG")
parser.set_defaults(points=",".join([str(i) for i in range(212)]))
args = parser.parse_args()
points = args.points.split(",")
points = [int(i) for i in points]
svg = points_to_svg(points)
if args.output:
with open(args.output, "w") as f:
f.write(svg)
if args.show:
import subprocess, os, platform
if platform.system() == 'Darwin': # macOS
subprocess.call(('open', args.output))
elif platform.system() == 'Windows': # Windows
os.startfile(args.output)
else: # linux variants
subprocess.call(('xdg-open', args.output))
if not args.show or not args.output:
print(svg)

View File

@@ -1,15 +0,0 @@
import os
from img_to_svg import img_to_svg
files = os.listdir("./imgs/twitter")
files = [os.path.join("./imgs/twitter", f) for f in files if f.endswith(".jpeg")]
for f in files:
output_path = f.replace(".jpeg", ".svg")
try:
img_to_svg(f, output_path)
except Exception as e:
print(f"Error processing {f}")
print(e)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -17,7 +17,6 @@ if __name__ == "__main__":
filename = args.filename
token = args.token
heights = get_heights(filename)
print(f"Heights: {heights}")
# drop the fist, last, and 12th bar
heights = heights[1:11] + heights[12:-1]
decoded = spotify_bar_decode(heights)

View File

@@ -1,78 +1,45 @@
from skimage import io
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu
from skimage.measure import label, regionprops
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from skimage import io
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu, gaussian
from skimage.morphology import closing, square
from skimage.measure import label, regionprops
import matplotlib.pyplot as plt
from skimage.color import rgb2gray
import matplotlib.patches as patches
def get_heights(filename: str) -> list:
"""Open an image and return a list of the bar heights.
Also saves three images: the processed binary image, the original image with drawings, and a smoothed binary image.
"""
from sklearn.cluster import KMeans
def get_heights(filename: str):
image = io.imread(filename)
im = rgb2gray(image)
binary_im = im > threshold_otsu(im)
smooth_im = gaussian(binary_im.astype(float), sigma=1)
morph_im = closing(smooth_im > 0.5, square(3))
# Apply Gaussian smoothing to binary image to make bars look rounder
smooth_im = gaussian(binary_im, sigma=2)
# Threshold again to obtain a smoothed binary
smooth_binary = smooth_im > 0.5
labeled = label(smooth_binary)
bar_dimensions = [r.bbox for r in regionprops(labeled)]
bar_dimensions.sort(key=lambda x: x[1]) # Sort left to right
# The first object (spotify logo) is the max height of the bars
logo = bar_dimensions[0]
max_height = logo[2] - logo[0]
sequence = []
# Create figure and axis for drawing
fig, ax = plt.subplots()
ax.imshow(smooth_binary, cmap='gray')
# Draw rectangle around logo
logo_rect = patches.Rectangle(
(logo[1], logo[0]),
logo[3]-logo[1],
logo[2]-logo[0],
linewidth=2,
edgecolor='yellow',
facecolor='none'
)
ax.add_patch(logo_rect)
# Add 'Logo' text near the rectangle
ax.text(logo[1], logo[0] - 10, 'Logo', color='yellow', fontsize=12, weight='bold')
# Draw bars and center markers
for bar in bar_dimensions[1:]:
height = bar[2] - bar[0]
ratio = height / max_height
ratio *= 8
ratio //= 1
val = int(ratio - 1)
sequence.append(val)
# Draw rectangle around bar
rect = patches.Rectangle(
(bar[1], bar[0]),
bar[3]-bar[1],
bar[2]-bar[0],
linewidth=1,
edgecolor='red',
facecolor='none'
)
ax.add_patch(rect)
# Draw center marker (white circle)
center_x = (bar[1] + bar[3]) / 2
center_y = (bar[0] + bar[2]) / 2
center_marker = patches.Circle((center_x, center_y), radius=5, color='black')
ax.add_patch(center_marker)
# Save processed binary image used for calculations
plt.imsave('processed_binary_image.png', binary_im, cmap='gray')
# Save smoothed binary image
plt.imsave('img_smooth.png', smooth_binary, cmap='gray')
# Save image with drawings
plt.axis('off')
plt.savefig('image_with_drawings.png', bbox_inches='tight', pad_inches=0)
plt.close()
return sequence
labeled = label(morph_im)
bar_dims = [r.bbox for r in regionprops(labeled)]
bar_dims.sort(key=lambda x: x[1]) # left to right
bars = bar_dims[1:] # skip logo
bar_heights_raw = []
for bar in bars:
top, left, bottom, right = bar
effective_height = bottom - top # use bounding box height directly
bar_heights_raw.append(effective_height)
print(len(bars))
# Cluster measured heights to 8 clusters representing discrete bar levels
bar_heights_raw_np = np.array(bar_heights_raw).reshape(-1, 1)
kmeans = KMeans(n_clusters=8, random_state=0).fit(bar_heights_raw_np)
cluster_centers = np.sort(kmeans.cluster_centers_.flatten())
# Assign each bar height to closest cluster center index (0 to 7)
predicted_levels = []
for h in bar_heights_raw:
diffs = np.abs(cluster_centers - h)
closest_cluster = np.argmin(diffs)
predicted_levels.append(closest_cluster)
print(len(predicted_levels))
return predicted_levels

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,11 +0,0 @@
import crc8
hash = crc8.crc8()
media_ref = 67775490487
ref_bytes = media_ref.to_bytes(5, byteorder="big")
print(ref_bytes)
# b'\x0f\xc7\xbb\xe9\xb7'
hash.update(ref_bytes)
check_bits = hash.digest()
print(check_bits)
# b'\x0c'

View File

@@ -1,52 +0,0 @@
from skimage import io
from skimage.measure import label, regionprops
from skimage.filters import threshold_otsu
from skimage.color import rgb2gray
from skimage import io
from skimage.color import rgb2gray
from skimage.filters import threshold_sauvola
from skimage.exposure import equalize_adapthist
from skimage.measure import label, regionprops
from skimage.morphology import closing, square
def get_heights(filename: str) -> list:
"""Open an image and return a list of the bar heights."""
# Load image and convert to grayscale
image = io.imread(filename)
im = rgb2gray(image)
# Apply adaptive histogram equalization to enhance contrast
im_eq = equalize_adapthist(im, clip_limit=0.03)
# Adaptive thresholding using Sauvola method
window_size = 25 # Adjust window size if needed
thresh_sauvola = threshold_sauvola(im_eq, window_size=window_size)
binary_im = im_eq > thresh_sauvola
# Morphological closing to fill gaps and clean noise
binary_im = closing(binary_im, square(3))
# Label connected components
labeled = label(binary_im)
# Get bounding boxes for each detected object
bar_dimensions = [r.bbox for r in regionprops(labeled)]
# Sort bounding boxes by horizontal position (xmin)
bar_dimensions.sort(key=lambda x: x[1])
# Use the first detected object (logo) as max height reference
logo = bar_dimensions[0]
max_height = logo[2] - logo[0]
sequence = []
for bar in bar_dimensions[1:]:
height = bar[2] - bar[0]
ratio = height / max_height
ratio *= 8
ratio = int(ratio) # truncate decimal part
sequence.append(ratio - 1)
return sequence