Gabor Cselle
commited on
Commit
•
5d8063e
1
Parent(s):
c99617f
HuggingFace commit
Browse files- .gitattributes +1 -0
- .gitignore +2 -1
- README.md +5 -1
- consts.py +2 -0
- gen_sample_data.py +60 -44
- visualize.ipynb +0 -0
.gitattributes
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
.gitignore
CHANGED
@@ -4,4 +4,5 @@ train_test_images
|
|
4 |
.ipynb_checkpoints/visualize-checkpoint.ipynb
|
5 |
font_identifier_model.pth
|
6 |
*.pyc
|
7 |
-
__pycache__
|
|
|
|
4 |
.ipynb_checkpoints/visualize-checkpoint.ipynb
|
5 |
font_identifier_model.pth
|
6 |
*.pyc
|
7 |
+
__pycache__
|
8 |
+
google_fonts
|
README.md
CHANGED
@@ -8,5 +8,9 @@ Follow along:
|
|
8 |
- [On Twitter](https://twitter.com/gabor/status/1722300841691103467)
|
9 |
|
10 |
Generate sample images (note this will work only on Mac): [gen_sample_data.py](gen_sample_data.py)
|
|
|
11 |
Arrange test images into test and train: [arrange_train_test_images.py](arrange_train_test_images.py)
|
12 |
-
|
|
|
|
|
|
|
|
8 |
- [On Twitter](https://twitter.com/gabor/status/1722300841691103467)
|
9 |
|
10 |
Generate sample images (note this will work only on Mac): [gen_sample_data.py](gen_sample_data.py)
|
11 |
+
|
12 |
Arrange test images into test and train: [arrange_train_test_images.py](arrange_train_test_images.py)
|
13 |
+
|
14 |
+
Train a ResNet18 on the data: [train_font_identifier.py](train_font_identifier.py)
|
15 |
+
|
16 |
+
License: MIT
|
consts.py
CHANGED
@@ -30,3 +30,5 @@ GEN_IMAGES_DIR = './generated_images'
|
|
30 |
TRAIN_TEST_IMAGES_DIR = './train_test_images'
|
31 |
# where to grab the font files from
|
32 |
FONT_FILE_DIRS = ['/System/Library/Fonts/', '/System/Library/Fonts/Supplemental/']
|
|
|
|
|
|
30 |
TRAIN_TEST_IMAGES_DIR = './train_test_images'
|
31 |
# where to grab the font files from
|
32 |
FONT_FILE_DIRS = ['/System/Library/Fonts/', '/System/Library/Fonts/Supplemental/']
|
33 |
+
# where to grab the Google Fonts, all of which are allowed
|
34 |
+
GOOGLE_FONTS_DIR = "./google_fonts"
|
gen_sample_data.py
CHANGED
@@ -6,66 +6,82 @@ from PIL import Image, ImageDraw, ImageFont
|
|
6 |
import nltk
|
7 |
from nltk.corpus import brown
|
8 |
import random
|
9 |
-
from consts import FONT_ALLOWLIST, IMAGES_PER_FONT, GEN_IMAGES_DIR, FONT_FILE_DIRS
|
10 |
|
11 |
# Download the necessary data from nltk
|
12 |
-
nltk.download('
|
13 |
|
14 |
os.makedirs(GEN_IMAGES_DIR, exist_ok=True)
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
def wrap_text(text, line_length=10):
|
19 |
"""Wraps the provided text every 'line_length' words."""
|
20 |
words = text.split()
|
21 |
return "\n".join([" ".join(words[i:i+line_length]) for i in range(0, len(words), line_length)])
|
22 |
|
23 |
-
def random_prose_text(
|
24 |
-
"""Returns a random
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
"""Returns a random selection of 'num_lines' lines from the provided code."""
|
30 |
-
lines = base_code.split("\n")
|
31 |
-
return "\n".join(random.sample(lines, min(num_lines, len(lines))))
|
32 |
|
33 |
def main():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
for font_dir in FONT_FILE_DIRS:
|
35 |
for font_file in os.listdir(font_dir):
|
36 |
if font_file.endswith('.ttf') or font_file.endswith('.ttc'):
|
37 |
font_path = os.path.join(font_dir, font_file)
|
38 |
font_name = font_file.split('.')[0]
|
39 |
-
if font_name
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
70 |
if __name__ == '__main__':
|
71 |
main()
|
|
|
6 |
import nltk
|
7 |
from nltk.corpus import brown
|
8 |
import random
|
9 |
+
from consts import FONT_ALLOWLIST, IMAGES_PER_FONT, GEN_IMAGES_DIR, FONT_FILE_DIRS, GOOGLE_FONTS_DIR
|
10 |
|
11 |
# Download the necessary data from nltk
|
12 |
+
nltk.download('inaugural')
|
13 |
|
14 |
os.makedirs(GEN_IMAGES_DIR, exist_ok=True)
|
15 |
|
16 |
+
def wrap_text(text, line_length=4):
|
|
|
|
|
17 |
"""Wraps the provided text every 'line_length' words."""
|
18 |
words = text.split()
|
19 |
return "\n".join([" ".join(words[i:i+line_length]) for i in range(0, len(words), line_length)])
|
20 |
|
21 |
+
def random_prose_text(line_length=4):
|
22 |
+
"""Returns a random snippet from the Gutenberg corpus."""
|
23 |
+
corpus = nltk.corpus.inaugural.raw()
|
24 |
+
start = random.randint(0, len(corpus) - 800)
|
25 |
+
end = start + 800
|
26 |
+
return wrap_text(corpus[start:end], line_length=line_length)
|
|
|
|
|
|
|
27 |
|
28 |
def main():
|
29 |
+
# Collect all allowed font files
|
30 |
+
font_files = []
|
31 |
+
# all of the Google fonts are allowed, no matter what
|
32 |
+
for font_file in os.listdir(GOOGLE_FONTS_DIR):
|
33 |
+
if font_file.endswith('.ttf') or font_file.endswith('.ttc'):
|
34 |
+
font_path = os.path.join(GOOGLE_FONTS_DIR, font_file)
|
35 |
+
font_name = font_file.split('.')[0]
|
36 |
+
font_files.append((font_path, font_name))
|
37 |
+
|
38 |
+
# for the system font directories, use the FONT_ALLOWLIST
|
39 |
for font_dir in FONT_FILE_DIRS:
|
40 |
for font_file in os.listdir(font_dir):
|
41 |
if font_file.endswith('.ttf') or font_file.endswith('.ttc'):
|
42 |
font_path = os.path.join(font_dir, font_file)
|
43 |
font_name = font_file.split('.')[0]
|
44 |
+
if font_name in FONT_ALLOWLIST:
|
45 |
+
font_files.append((font_path, font_name))
|
46 |
+
|
47 |
+
# Generate images for each font file
|
48 |
+
for font_path, font_name in font_files:
|
49 |
+
# Output the font name so we can see the progress
|
50 |
+
print(font_path, font_name)
|
51 |
+
|
52 |
+
# Random font size
|
53 |
+
font_size = random.choice(range(18, 72))
|
54 |
+
|
55 |
+
if font_path.endswith('.ttc'):
|
56 |
+
# ttc fonts have multiple fonts in one file, so we need to specify which one we want
|
57 |
+
font = ImageFont.truetype(font_path, font_size, index=0)
|
58 |
+
else:
|
59 |
+
# ttf fonts have only one font in the file
|
60 |
+
font = ImageFont.truetype(font_path, font_size)
|
61 |
+
|
62 |
+
# Counter for the image filename
|
63 |
+
j = 0
|
64 |
+
for i in range(IMAGES_PER_FONT): # Generate 50 images per font - reduced to 10 for now to make things faster
|
65 |
+
# Determine the number of words that will fit on a line
|
66 |
+
font_avg_char_width = font.getbbox('x')[2]
|
67 |
+
words_per_line = int(800 / (font_avg_char_width*5))
|
68 |
+
prose_sample = random_prose_text(line_length=words_per_line)
|
69 |
+
|
70 |
+
for text in [prose_sample]:
|
71 |
+
img = Image.new('RGB', (800, 400), color="white") # Canvas size
|
72 |
+
draw = ImageDraw.Draw(img)
|
73 |
+
|
74 |
+
# Random offsets, but ensuring that text isn't too far off the canvas
|
75 |
+
offset_x = random.randint(-20, 10)
|
76 |
+
offset_y = random.randint(-20, 10)
|
77 |
+
|
78 |
+
# vary the line height
|
79 |
+
line_height = random.uniform(0, 1.25) * font_size
|
80 |
+
draw.text((offset_x, offset_y), text, fill="black", font=font, spacing=line_height)
|
81 |
+
|
82 |
+
j += 1
|
83 |
+
output_file = os.path.join(GEN_IMAGES_DIR, f"{font_name}_{j}.png")
|
84 |
+
img.save(output_file)
|
85 |
|
86 |
if __name__ == '__main__':
|
87 |
main()
|
visualize.ipynb
CHANGED
The diff for this file is too large to render.
See raw diff
|
|