version k-means qui marche!!
@@ -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)
|
||||||
2
boonepeter.github.io-code/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
__pycache__
|
|
||||||
.venv
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
@@ -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:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
The svg is useful for checking that the point parsing was correct.
|
|
||||||
@@ -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
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/imgs
|
|
||||||
.ipynb_checkpoints/
|
|
||||||
|
Before Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 119 KiB |
@@ -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))
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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)
|
|
||||||
@@ -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 |
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
|
Before Width: | Height: | Size: 85 KiB |
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -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'
|
|
||||||
@@ -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
|
|
||||||
1
final-loop/spotify-codes-part-2/.cache
Normal 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}
|
||||||
21
final-loop/spotify-codes-part-2/requirements.txt
Normal 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
|
||||||
31
final-loop/spotify-codes-part-2/src/crc.py
Normal 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}")
|
||||||
57
final-loop/spotify-codes-part-2/src/decode_barcode.py
Normal 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()
|
||||||
118
final-loop/spotify-codes-part-2/src/encode_decode.py
Normal 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
|
||||||
48
final-loop/spotify-codes-part-2/src/get_heights.py
Normal 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
|
||||||
56
final-loop/spotify-codes-part-2/src/get_uri.py
Normal 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
|
||||||
|
|
||||||
11
final-loop/spotify-codes-part-2/src/permute.py
Normal 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
|
||||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -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))
|
||||||
15
final-loop/spotify-codes-part-2/src/step_by_step.py
Normal 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))
|
||||||
BIN
mid_image.jpg
|
Before Width: | Height: | Size: 468 KiB |
BIN
rebirth.jpg
Normal file
|
After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 59 KiB |
@@ -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()
|
|
||||||