fichiers mec la
2
boonepeter.github.io-code/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
__pycache__
|
||||||
|
.venv
|
||||||
12
boonepeter.github.io-code/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
37
boonepeter.github.io-code/snapcodes/README.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# 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.
|
||||||
10
boonepeter.github.io-code/snapcodes/requirements.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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
|
||||||
2
boonepeter.github.io-code/snapcodes/src/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/imgs
|
||||||
|
.ipynb_checkpoints/
|
||||||
1661
boonepeter.github.io-code/snapcodes/src/exploring/process.ipynb
Normal file
BIN
boonepeter.github.io-code/snapcodes/src/generated/all_dots.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 106 KiB |
BIN
boonepeter.github.io-code/snapcodes/src/generated/mask.jpeg
Normal file
|
After Width: | Height: | Size: 119 KiB |
305
boonepeter.github.io-code/snapcodes/src/img_to_points.py
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
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))
|
||||||
|
|
||||||
|
|
||||||
21
boonepeter.github.io-code/snapcodes/src/img_to_svg.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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)
|
||||||
300
boonepeter.github.io-code/snapcodes/src/output.svg
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
<?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>
|
||||||
|
After Width: | Height: | Size: 10 KiB |
78
boonepeter.github.io-code/snapcodes/src/points_to_svg.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
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)
|
||||||
15
boonepeter.github.io-code/snapcodes/src/process_all.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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)
|
||||||
BIN
boonepeter.github.io-code/snapcodes/src/snapcode.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
1
boonepeter.github.io-code/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}
|
||||||
32
boonepeter.github.io-code/spotify-codes-part-2/README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Spotify Codes Part 2
|
||||||
|
|
||||||
|
## Install packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip3 install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python decode_barcode.py --token="YOUR_TOKEN_HERE" ./pics/spotify_playlist_37i9dQZF1DXcBWIGoYBM5M.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Token
|
||||||
|
|
||||||
|
This script requires your authorization token to run. You can get this (manually) by visiting [Spotify's Web client](https://open.spotify.com/). If you inspect the page source (F12), you can search for `access_token` in the Network tab after reloading the page. Copy the value and paste it into the `--token` argument.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Heights: [0, 6, 0, 2, 4, 5, 1, 4, 5, 2, 3, 7, 3, 7, 1, 5, 6, 2, 5, 7, 4, 3, 0]
|
||||||
|
Media ref: 57268659651
|
||||||
|
URI: spotify:user:spotify:playlist:37i9dQZF1DXcBWIGoYBM5M
|
||||||
|
Summary:
|
||||||
|
{
|
||||||
|
'description': 'Coldplay & BTS are on top of the Hottest 50!',
|
||||||
|
'name': "Today's Top Hits",
|
||||||
|
'type': 'playlist',
|
||||||
|
'url': 'https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M'
|
||||||
|
}
|
||||||
|
```
|
||||||
BIN
boonepeter.github.io-code/spotify-codes-part-2/aa.jpg
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/biz.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/biz.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/bizrog.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/bw.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/bz.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/code.jpeg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/csq.jpg
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/imperdable.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/jtd.jpg
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/jtd.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/lala.jpeg
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/layayaya.jpg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/maybe.jpg
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/maybe.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/nggyu.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/nggyu.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/parfd.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/print.jpg
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/rage.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/rbw.jpg
Normal file
|
After Width: | Height: | Size: 109 KiB |
@@ -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
|
||||||
BIN
boonepeter.github.io-code/spotify-codes-part-2/rog.jpg
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/rogprint.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/rouuuge.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/rr.jpeg
Normal file
|
After Width: | Height: | Size: 12 KiB |
31
boonepeter.github.io-code/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}")
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Spotify Barcode Decoder.")
|
||||||
|
|
||||||
|
parser.add_argument("filename", help="The filename of the barcode to decode.")
|
||||||
|
parser.add_argument("--token", required=True, help="Your Spotify authorization token.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = parser.parse_args()
|
||||||
|
filename = args.filename
|
||||||
|
token = args.token
|
||||||
|
heights = get_heights(filename)
|
||||||
|
print(f"Heights: {heights}")
|
||||||
|
# drop the fist, last, and 12th bar
|
||||||
|
heights = heights[1:11] + heights[12:-1]
|
||||||
|
decoded = spotify_bar_decode(heights)
|
||||||
|
print(f"Media ref: {decoded}")
|
||||||
|
uri = get_uri(decoded, token)
|
||||||
|
print(f"URI: {uri['target']}")
|
||||||
|
summary, full_response = get_info(uri["target"], token)
|
||||||
|
print("Summary:")
|
||||||
|
pprint.pprint(summary)
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
from skimage import io
|
||||||
|
from skimage.color import rgb2gray
|
||||||
|
from skimage.filters import threshold_otsu
|
||||||
|
from skimage.measure import label, regionprops
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.patches as patches
|
||||||
|
|
||||||
|
from skimage import io
|
||||||
|
from skimage.color import rgb2gray
|
||||||
|
from skimage.filters import threshold_otsu, gaussian
|
||||||
|
from skimage.measure import label, regionprops
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.patches as patches
|
||||||
|
|
||||||
|
def get_heights(filename: str) -> list:
|
||||||
|
"""Open an image and return a list of the bar heights.
|
||||||
|
Also saves three images: the processed binary image, the original image with drawings, and a smoothed binary image.
|
||||||
|
"""
|
||||||
|
image = io.imread(filename)
|
||||||
|
im = rgb2gray(image)
|
||||||
|
binary_im = im > threshold_otsu(im)
|
||||||
|
|
||||||
|
# Apply Gaussian smoothing to binary image to make bars look rounder
|
||||||
|
smooth_im = gaussian(binary_im, sigma=2)
|
||||||
|
# Threshold again to obtain a smoothed binary
|
||||||
|
smooth_binary = smooth_im > 0.5
|
||||||
|
|
||||||
|
labeled = label(smooth_binary)
|
||||||
|
bar_dimensions = [r.bbox for r in regionprops(labeled)]
|
||||||
|
bar_dimensions.sort(key=lambda x: x[1]) # Sort left to right
|
||||||
|
|
||||||
|
# The first object (spotify logo) is the max height of the bars
|
||||||
|
logo = bar_dimensions[0]
|
||||||
|
max_height = logo[2] - logo[0]
|
||||||
|
|
||||||
|
sequence = []
|
||||||
|
|
||||||
|
# Create figure and axis for drawing
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.imshow(smooth_binary, cmap='gray')
|
||||||
|
|
||||||
|
# Draw rectangle around logo
|
||||||
|
logo_rect = patches.Rectangle(
|
||||||
|
(logo[1], logo[0]),
|
||||||
|
logo[3]-logo[1],
|
||||||
|
logo[2]-logo[0],
|
||||||
|
linewidth=2,
|
||||||
|
edgecolor='yellow',
|
||||||
|
facecolor='none'
|
||||||
|
)
|
||||||
|
ax.add_patch(logo_rect)
|
||||||
|
|
||||||
|
# Add 'Logo' text near the rectangle
|
||||||
|
ax.text(logo[1], logo[0] - 10, 'Logo', color='yellow', fontsize=12, weight='bold')
|
||||||
|
|
||||||
|
# Draw bars and center markers
|
||||||
|
for bar in bar_dimensions[1:]:
|
||||||
|
height = bar[2] - bar[0]
|
||||||
|
ratio = height / max_height
|
||||||
|
ratio *= 8
|
||||||
|
ratio //= 1
|
||||||
|
val = int(ratio - 1)
|
||||||
|
sequence.append(val)
|
||||||
|
|
||||||
|
# Draw rectangle around bar
|
||||||
|
rect = patches.Rectangle(
|
||||||
|
(bar[1], bar[0]),
|
||||||
|
bar[3]-bar[1],
|
||||||
|
bar[2]-bar[0],
|
||||||
|
linewidth=1,
|
||||||
|
edgecolor='red',
|
||||||
|
facecolor='none'
|
||||||
|
)
|
||||||
|
ax.add_patch(rect)
|
||||||
|
|
||||||
|
# Draw center marker (white circle)
|
||||||
|
center_x = (bar[1] + bar[3]) / 2
|
||||||
|
center_y = (bar[0] + bar[2]) / 2
|
||||||
|
center_marker = patches.Circle((center_x, center_y), radius=5, color='black')
|
||||||
|
ax.add_patch(center_marker)
|
||||||
|
|
||||||
|
# Save processed binary image used for calculations
|
||||||
|
plt.imsave('processed_binary_image.png', binary_im, cmap='gray')
|
||||||
|
|
||||||
|
# Save smoothed binary image
|
||||||
|
plt.imsave('img_smooth.png', smooth_binary, cmap='gray')
|
||||||
|
|
||||||
|
# Save image with drawings
|
||||||
|
plt.axis('off')
|
||||||
|
plt.savefig('image_with_drawings.png', bbox_inches='tight', pad_inches=0)
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
return sequence
|
||||||
@@ -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
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -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 |
|
After Width: | Height: | Size: 18 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))
|
||||||
@@ -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
boonepeter.github.io-code/spotify-codes-part-2/st.jpeg
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/test.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
boonepeter.github.io-code/spotify-codes-part-2/testrouge.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
11
boonepeter.github.io-code/spotify-codes/calc_crc8.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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'
|
||||||
52
boonepeter.github.io-code/spotify-codes/get_heights.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||