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

@@ -69,10 +69,11 @@ def image_for_mathias(name="1.jpg"):
# Show result # Show result
cv2.imshow("Spotify Code Deskewed", warped) cv2.imshow("Spotify Code Deskewed", warped)
cv2.waitKey(0) cv2.waitKey(0)
cv2.destroyAllWindows() cv2.destroyAllWindows()
l = ["1.jpg", "2.jpg", "3.jpg", "4.jpg"] l = ["rebirth.jpg"]
for name in l: for name in l:
image_for_mathias(name) image_for_mathias(name)

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 filename = args.filename
token = args.token token = args.token
heights = get_heights(filename) heights = get_heights(filename)
print(f"Heights: {heights}")
# drop the fist, last, and 12th bar # drop the fist, last, and 12th bar
heights = heights[1:11] + heights[12:-1] heights = heights[1:11] + heights[12:-1]
decoded = spotify_bar_decode(heights) decoded = spotify_bar_decode(heights)

View File

@@ -1,78 +1,45 @@
from skimage import io import numpy as np
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu
from skimage.measure import label, regionprops
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.patches as patches
from skimage import io from skimage import io
from skimage.color import rgb2gray
from skimage.filters import threshold_otsu, gaussian from skimage.filters import threshold_otsu, gaussian
from skimage.morphology import closing, square
from skimage.measure import label, regionprops from skimage.measure import label, regionprops
import matplotlib.pyplot as plt from skimage.color import rgb2gray
import matplotlib.patches as patches import matplotlib.patches as patches
def get_heights(filename: str) -> list: from sklearn.cluster import KMeans
"""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. def get_heights(filename: str):
"""
image = io.imread(filename) image = io.imread(filename)
im = rgb2gray(image) im = rgb2gray(image)
binary_im = im > threshold_otsu(im) 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 labeled = label(morph_im)
smooth_im = gaussian(binary_im, sigma=2) bar_dims = [r.bbox for r in regionprops(labeled)]
# Threshold again to obtain a smoothed binary bar_dims.sort(key=lambda x: x[1]) # left to right
smooth_binary = smooth_im > 0.5
labeled = label(smooth_binary) bars = bar_dims[1:] # skip logo
bar_dimensions = [r.bbox for r in regionprops(labeled)] bar_heights_raw = []
bar_dimensions.sort(key=lambda x: x[1]) # Sort left to right
# The first object (spotify logo) is the max height of the bars for bar in bars:
logo = bar_dimensions[0] top, left, bottom, right = bar
max_height = logo[2] - logo[0] effective_height = bottom - top # use bounding box height directly
sequence = [] bar_heights_raw.append(effective_height)
# Create figure and axis for drawing print(len(bars))
fig, ax = plt.subplots() # Cluster measured heights to 8 clusters representing discrete bar levels
ax.imshow(smooth_binary, cmap='gray') bar_heights_raw_np = np.array(bar_heights_raw).reshape(-1, 1)
# Draw rectangle around logo
logo_rect = patches.Rectangle( kmeans = KMeans(n_clusters=8, random_state=0).fit(bar_heights_raw_np)
(logo[1], logo[0]), cluster_centers = np.sort(kmeans.cluster_centers_.flatten())
logo[3]-logo[1],
logo[2]-logo[0], # Assign each bar height to closest cluster center index (0 to 7)
linewidth=2, predicted_levels = []
edgecolor='yellow', for h in bar_heights_raw:
facecolor='none' diffs = np.abs(cluster_centers - h)
) closest_cluster = np.argmin(diffs)
ax.add_patch(logo_rect) predicted_levels.append(closest_cluster)
# 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 print(len(predicted_levels))
for bar in bar_dimensions[1:]: return predicted_levels
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

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

View File

@@ -0,0 +1 @@
{"access_token": "BQAHRFXWQMMKWNbUO3sfhk92iUGi5VJNZmB2lTjRim-g2AIe88mgDvyc1NywjCVBTeZK-b3PkXzGDANJDDOouitEt5P8z0jpVuPrpalwAg0PzEwtKBXr8PHvacajO9B3s7Wy0s7G3CFlfGfbOfmXtW_vF8G2_ooGnlFp_oM8RcOyBl7D5Okf6kr07CWIJty4mNeTf1Ifd212u1tBnbU64lckqlQ0f0W105mt", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "AQCSe9ARCDB-Q6gzna_KKAlcEuNOx2IC9BSVXS5GZxNDi4AjJdA4Kn3hp1YPfiPfogd2rz-5zLbyjUxiRj9oTd-q5EItEPZHIVt-eMf7gCGHfj6h-hLpRrnhgMZjzRAS7CY", "scope": "user-modify-playback-state user-read-playback-state", "expires_at": 1757966002}

View File

@@ -0,0 +1,21 @@
certifi>=2023.5.7
chardet>=5.1.0
crccheck>=1.0
cycler>=0.11.0
decorator>=5.1.1
idna>=3.5
imageio>=2.31.1
kiwisolver>=1.4.4
matplotlib>=3.7.1
networkx>=3.1
numpy>=1.24.2
Pillow>=10.0.0
pyparsing>=3.0.9
python-dateutil>=2.8.2
PyWavelets>=1.4.1
requests>=2.31.0
scikit-image>=0.25.2
scipy>=1.11.1
six>=1.16.0
tifffile>=2023.7.23
urllib3>=2.0.4

View File

@@ -0,0 +1,31 @@
def crc(data, polynomial):
n = len(polynomial) - 1
initial_length = len(data)
check_bits = data + [0] * n
for i in range(initial_length):
if check_bits[i] == 1:
for j, p in enumerate(polynomial):
check_bits[i + j] = check_bits[i + j] ^ p
return check_bits[-n:]
def check_crc(data, polynomial, check_bits):
full_data = data + check_bits
for i in range(len(data)):
if full_data[i] == 1:
for j, p in enumerate(polynomial):
full_data[i + j] = full_data[i + j] ^ p
return 1 not in full_data
data = [0,0,1,0,0,0,1,0,1,1,1,1,0,1,1,1,1,0,0,0,1,1,1,1,0,1,1,0,1,0,1,1,0,0,0,0,1,1,0,1]
check = [1,1,0,0,1,1,0,0]
poly = [1,0,0,0,0,0,1,1,1]
print(check_crc(data, poly, check))
if __name__ == "__main__":
example = "0010001011110111100011110110101100001101"
long = [int(i) for i in example]
polynomial = [1, 0, 0, 0, 0, 0, 1, 1, 1] # crc8 polynomial
check = crc(long, polynomial)
print(f"Check bits: {check}")
checked = check_crc(long, polynomial, check)
print(f"Checked: {checked}")

View File

@@ -0,0 +1,57 @@
import argparse
import pprint
from get_heights import get_heights
from encode_decode import spotify_bar_decode
from get_uri import get_uri, get_info
import cv2
def process_frame(frame, token):
heights = get_heights(frame)
print(len(heights)) # assumes get_heights can take image array
if len(heights) != 23:
return None # skip bad frames
else:
print("ON TROUVE UN CODE")
heights = heights[1:11] + heights[12:-1]
decoded = spotify_bar_decode(heights)
uri = get_uri(decoded, token)
summary, full_response = get_info(uri["target"], token)
return summary
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--token", required=True)
args = parser.parse_args()
token = args.token
cap = cv2.VideoCapture(0) # 0 for default webcam
while True:
ret, frame = cap.read()
if not ret:
break
# Définir les dimensions et la marge du cadre
height, width, _ = frame.shape
margin = 100 # ajustez la taille du cadre ici
frame = frame[margin:height - margin, margin:width - margin]
try:
summary = process_frame(frame, token)
except Exception:
continue
if summary:
print("Summary:")
pprint.pprint(summary)
cv2.imshow("Live Barcode", frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()

View File

@@ -0,0 +1,118 @@
import numpy as np
import crccheck
# This code was written by "Doyle" on Stack Overflow
# https://stackoverflow.com/a/64950150/10703868
# Utils for conversion between int, array of binary
# and array of bytes (as ints)
def int_to_bin(num, length, endian):
if endian == 'l':
return [num >> i & 1 for i in range(0, length)]
elif endian == 'b':
return [num >> i & 1 for i in range(length-1, -1, -1)]
def bin_to_int(bin,length):
return int("".join([str(bin[i]) for i in range(length-1,-1,-1)]),2)
def bin_to_bytes(bin, length):
b = bin[0:length] + [0] * (-length % 8)
return [(b[i]<<7) + (b[i+1]<<6) + (b[i+2]<<5) + (b[i+3]<<4) +
(b[i+4]<<3) + (b[i+5]<<2) + (b[i+6]<<1) + b[i+7] for i in range(0,len(b),8)]
# Return the circular right shift of an array by 'n' positions
def shift_right(arr, n):
return arr[-n % len(arr):len(arr):] + arr[0:-n % len(arr)]
gray_code = [0,1,3,2,7,6,4,5]
gray_code_inv = [[0,0,0],[0,0,1],[0,1,1],[0,1,0],
[1,1,0],[1,1,1],[1,0,1],[1,0,0]]
# CRC using Rocksoft model:
# NOTE: this is not quite any of their predefined CRC's
# 8: number of check bits (degree of poly)
# 0x7: representation of poly without high term (x^8+x^2+x+1)
# 0x0: initial fill of register
# True: byte reverse data
# True: byte reverse check
# 0xff: Mask check (i.e. invert)
spotify_crc = crccheck.crc.Crc(8, 0x7, 0x0, True, True, 0xff)
def calc_spotify_crc(bin37):
bytes = bin_to_bytes(bin37, 37)
return int_to_bin(spotify_crc.calc(bytes), 8, 'b')
def check_spotify_crc(bin45):
data = bin_to_bytes(bin45,37)
return spotify_crc.calc(data) == bin_to_bytes(bin45[37:], 8)[0]
# Simple convolutional encoder
def encode_cc(dat):
gen1 = [1,0,1,1,0,1,1]
gen2 = [1,1,1,1,0,0,1]
punct = [1,1,0]
dat_pad = dat[-6:] + dat # 6 bits are needed to initialize
# register for tail-biting
stream1 = np.convolve(dat_pad, gen1, mode='valid') % 2
stream2 = np.convolve(dat_pad, gen2, mode='valid') % 2
enc = [val for pair in zip(stream1, stream2) for val in pair]
return [enc[i] for i in range(len(enc)) if punct[i % 3]]
# To create a generator matrix for a code, we encode each row
# of the identity matrix. Note that the CRC is not quite linear
# because of the check mask so we apply the lamda function to
# invert it. Given a 37 bit media reference we can encode by
# ref * spotify_generator + spotify_mask (mod 2)
_i37 = np.identity(37, dtype=bool)
crc_generator = [_i37[r].tolist() +
list(map(lambda x : 1-x, calc_spotify_crc(_i37[r].tolist())))
for r in range(37)]
spotify_generator = 1*np.array([encode_cc(crc_generator[r]) for r in range(37)], dtype=bool)
del _i37
spotify_mask = 1*np.array(encode_cc(37*[0] + 8*[1]), dtype=bool)
# The following matrix is used to "invert" the convolutional code.
# In particular, we choose a 45 vector basis for the columns of the
# generator matrix (by deleting those in positions equal to 2 mod 4)
# and then inverting the matrix. By selecting the corresponding 45
# elements of the convolutionally encoded vector and multiplying
# on the right by this matrix, we get back to the unencoded data,
# assuming there are no errors.
# Note: numpy does not invert binary matrices, i.e. GF(2), so we
# hard code the following 3 row vectors to generate the matrix.
conv_gen = [[0,1,0,1,1,1,1,0,1,1,0,0,0,1]+31*[0],
[1,0,1,0,1,0,1,0,0,0,1,1,1] + 32*[0],
[0,0,1,0,1,1,1,1,1,1,0,0,1] + 32*[0] ]
conv_generator_inv = 1*np.array([shift_right(conv_gen[(s-27) % 3],s) for s in range(27,72)], dtype=bool)
# Given an integer media reference, returns list of 20 barcode levels
def spotify_bar_code(ref):
bin37 = np.array([int_to_bin(ref, 37, 'l')], dtype=bool)
enc = (np.add(1*np.dot(bin37, spotify_generator), spotify_mask) % 2).flatten()
perm = [enc[7*i % 60] for i in range(60)]
return [gray_code[4*perm[i]+2*perm[i+1]+perm[i+2]] for i in range(0,len(perm),3)]
# Equivalent function but using CRC and CC encoders.
def spotify_bar_code2(ref):
bin37 = int_to_bin(ref, 37, 'l')
enc_crc = bin37 + calc_spotify_crc(bin37)
enc_cc = encode_cc(enc_crc)
perm = [enc_cc[7*i % 60] for i in range(60)]
return [gray_code[4*perm[i]+2*perm[i+1]+perm[i+2]] for i in range(0,len(perm),3)]
# Given 20 (clean) barcode levels, returns media reference
def spotify_bar_decode(levels):
level_bits = np.array([gray_code_inv[levels[i]] for i in range(20)], dtype=bool).flatten()
conv_bits = [level_bits[43*i % 60] for i in range(60)]
cols = [i for i in range(60) if i % 4 != 2] # columns to invert
conv_bits45 = np.array([conv_bits[c] for c in cols], dtype=bool)
bin45 = (1*np.dot(conv_bits45, conv_generator_inv) % 2).tolist()
if check_spotify_crc(bin45):
return bin_to_int(bin45, 37)
else:
print('Error in levels; Use real decoder!!!')
return -1

View File

@@ -0,0 +1,48 @@
import numpy as np
import matplotlib.pyplot as plt
from skimage import io
from skimage.filters import threshold_otsu, gaussian
from skimage.morphology import closing, square
from skimage.measure import label, regionprops
from skimage.color import rgb2gray
import matplotlib.patches as patches
from sklearn.cluster import KMeans
import warnings
warnings.filterwarnings("ignore", category=FutureWarning, message=".*square.*deprecated.*")
def get_heights(image):
# 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))
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 = []
if(len(bars)!=23):
return [0]
for bar in bars:
top, left, bottom, right = bar
effective_height = bottom - top # use bounding box height directly
bar_heights_raw.append(effective_height)
# 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(int(closest_cluster))
return predicted_levels

View File

@@ -0,0 +1,56 @@
from typing import Tuple
import requests
HEADERS_LUT = {
"X-Client-Id": "58bd3c95768941ea9eb4350aaa033eb3",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"App-Platform": "iOS",
"Accept": "*/*",
"User-Agent": "Spotify/8.5.68 iOS/13.4 (iPhone9,3)",
"Accept-Language": "en",
"Spotify-App-Version": "8.5.68",
}
MEDIA_REF_LUT_URL = "https://spclient.wg.spotify.com:443/scannable-id/id"
def get_uri(media_ref: int, token: str):
"""Query Spotify internal API to get the URI of the media reference."""
header = {
**HEADERS_LUT,
"Authorization": f"Bearer {token}"
}
url = f'{MEDIA_REF_LUT_URL}/{media_ref}?format=json'
response = requests.get(url, headers=header)
response.raise_for_status()
return response.json()
def get_info(uri: str, token: str) -> Tuple[dict, dict]:
"""Query the Spotify API to get information about a URI."""
info_headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": f"Bearer {token}"
}
split = uri.split(":")
content_type = split[-2] + "s"
id = split[-1]
response = requests.get(f"https://api.spotify.com/v1/{content_type}/{id}", headers=info_headers)
response.raise_for_status()
resp = response.json()
result = {
"name": resp["name"],
"type": split[-2],
"url": resp["external_urls"]["spotify"],
}
if "artists" in resp:
result['artists'] = []
for a in resp['artists']:
result["artists"].append(a["name"])
if "album" in resp:
result['album'] = resp['album']['name']
if "description" in resp:
result["description"] = resp['description']
return result, resp

View File

@@ -0,0 +1,11 @@
def permute(bits, step=7):
for i in range(len(bits)):
yield bits[(i * step) % len(bits)]
if __name__ == "__main__":
bits = "111000111100101111101110111001011100110000100100011100110011"
print("".join(permute(bits)))
# 111100110001110101101000011110010110101100111111101000111000

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,35 @@
from typing import List
def encode(bits: List[int], polynomial: List[int], tail_bite=False):
"""Convolutionally encode the stream of bits using the generator polynomial.
If tail_bite == True, prepend the tail of the input. Otherwise use 0s to fill.
"""
if tail_bite:
tail = bits[-(len(polynomial) - 1):]
else:
tail = [0 for i in range(len(polynomial) - 1)]
full = tail + bits
polynomial.reverse() # Reverse since we're working the other direction
parity_bits = []
for i in range(len(bits)):
parity = 0
for j in range(len(polynomial)):
parity ^= full[i + j] * polynomial[j]
parity_bits.append(parity)
return parity_bits
if __name__ == "__main__":
g0 = [1, 0, 1, 1, 0, 1, 1]
g1 = [1, 1, 1, 1, 0, 0, 1]
bits = "010001001110111111110001110101101011011001100"
g0_expected = "100011100111110100110011110100000010001001011"
g1_expected = "110011100010110110110100101101011100110011011"
bits = [int(i) for i in bits]
p0 = encode(bits, g0, True)
p1 = encode(bits, g1, True)
print(g0_expected == "".join(str(i) for i in p0))
print(g1_expected == "".join(str(i) for i in p1))

View File

@@ -0,0 +1,15 @@
media_ref = 57639171874
binary = f"{bin(media_ref)[2:]:0>37}"
print(binary)
# pad with 3 bits to the right:
binary = f"{binary:0<39}"
print(binary)
a = "100011100111110100110011110100000010001001011"
b = "110011100010110110110100101101011100110011011"
c = zip(a, b)
print("".join(i + j for i, j in c))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 KiB

BIN
rebirth.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -1,44 +0,0 @@
import cv2
from ultralytics import YOLO
model = YOLO('yolov5s.pt') # Replace with your trained logo detection model path
cap = cv2.VideoCapture(0) # Webcam input
if not cap.isOpened():
print("Error: Could not open video stream.")
exit()
while True:
ret, frame = cap.read()
if not ret:
break
results = model(frame)
for result in results:
boxes = result.boxes.xyxy.cpu().numpy()
scores = result.boxes.conf.cpu().numpy()
class_ids = result.boxes.cls.cpu().numpy()
for box, score, cls_id in zip(boxes, scores, class_ids):
if score < 0.3:
continue
x1, y1, x2, y2 = map(int, box)
label = model.names[int(cls_id)]
# Draw bounding box and label
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(frame, f'{label} {score:.2f}', (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# Log detection to console
print(f"Detected: {label} | Confidence: {score:.2f} | Box: ({x1}, {y1}), ({x2}, {y2})")
cv2.imshow('Logo Detection', frame)
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()

Binary file not shown.