Created
July 8, 2018 19:50
-
-
Save johnhw/ac6c788b36454aecdf0135e978c92e4b to your computer and use it in GitHub Desktop.
stitches.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import matplotlib.pyplot as plt | |
import numpy as np | |
import fractions | |
def lcm(a,b): | |
return abs(a * b) / fractions.gcd(a,b) if a and b else 0 | |
def hex_rgb_to_float(hex): | |
r = int(hex[0:2],16)/255.0 | |
g = int(hex[2:4],16)/255.0 | |
b = int(hex[4:6],16)/255.0 | |
return (r,g,b) | |
def clr(hex): | |
return hex_rgb_to_float(hex) | |
colorlist = """ | |
eucalyptus 355550 | |
nighthawk 004044 | |
caspian 338086 | |
petrol 005388 | |
""" | |
colors = {} | |
for color in colorlist.splitlines(): | |
color = color.strip() | |
parts = color.split() | |
if len(parts)==2: | |
colors[parts[0].strip()] = clr(parts[1].strip()) | |
class Stitches: | |
def __init__(self, init=None): | |
"""Create a new pattern, either from a numpy array | |
or from an ASCII string which will be converted | |
by read_pattern()"""" | |
if init is None: | |
self.pattern = None | |
elif type(init)==type(""): | |
self.pattern = self.read_pattern(init) | |
else: | |
self.pattern = init | |
def read_pattern(self, string): | |
"""Convert a pattern from ASCII to a stitch pattern. | |
Recognises any of *@=x#X as "on", anything else as off. | |
e.g. | |
oxo | |
xxx | |
oxo | |
and | |
-*- | |
*** | |
-*- | |
are equivalent. | |
Optionally, can specify a pair of color indices as single digits | |
specified with a space after a row. For example: | |
-*- 02 | |
*** 01 | |
-*- 34 | |
specifies color 0 (for -) and color 2 (for *) in the first row, | |
color 0 and color 1 for the second | |
and color 3 and color 4 for the third row. These can be omitted | |
as required. Color indices 0-9A-Z are valid""" | |
rows = string.splitlines() | |
stitches = [] | |
for row in rows: | |
row = row.strip() | |
if len(row)>0: | |
parts = row.split() | |
def add_row(row_chars, a, b): | |
stitch_row = [] | |
for ch in row_chars: | |
if ch in "@*=x#X": | |
stitch_row.append(b) | |
else: | |
stitch_row.append(a) | |
stitches.append(stitch_row) | |
if len(parts)==1: # no color indicated | |
add_row(parts[0], 0, 1) | |
if len(parts)==2: # colors indicated | |
cols = int(parts[1][0],36), int(parts[1][1],36) | |
add_row(parts[0], cols[0], cols[1]) | |
return np.array(stitches) | |
def recolor(self, colors): | |
"""Recolor a pattern. Colors should be a list of | |
tuples of (original, replacement) color indices""" | |
recolored = np.array(self.pattern) | |
for fromc, toc in colors: | |
recolored[self.pattern==fromc] = toc | |
return Stitches(recolored) | |
def fliplr(self): | |
"""Flip pattern left to right (horizontal flip about vertical axis)""" | |
return Stitches(np.fliplr(self.pattern)) | |
def flipud(self): | |
"""Flip pattern top to bottom (vertical flip about horizontal axis)""" | |
return Stitches(np.flipud(self.pattern)) | |
def rot90(self): | |
"""Rotate pattern 90 degrees""" | |
return Stitches(np.rot90(self.pattern)) | |
def transpose(self): | |
"""Flip stitch pattern across diagonal (exchange rows and columns)""" | |
return Stitches(self.pattern.T) | |
def __add__(self, other): | |
"""Stack two stitch blocks horizontally. Must have | |
same row height""" | |
return Stitches(np.hstack((self.pattern, other.pattern))) | |
def __mul__(self, n): | |
"""Repeat a pattern horizontally n times""" | |
return Stitches(np.tile(self.pattern, (1, n))) | |
def __and__(self, other): | |
"""Stack two patterns vertically; must have same width""" | |
return Stitches(np.vstack((self.pattern, other.pattern))) | |
def __pow__(self, n): | |
"""Repeat a pattern vertically n times""" | |
return Stitches(np.tile(self.pattern, (n, 1))) | |
def __or__(self, other): | |
"""Stack two rows vertically, repeating patterns | |
until the result is a rectangular array (using least | |
common multiple of pattern widths)""" | |
c1, c2 = self.cols(), other.cols() | |
mult_row = lcm(c1,c2) | |
top = self * int(mult_row//c1) | |
bottom = other * int(mult_row//c2) | |
s = Stitches(np.vstack((top.pattern, bottom.pattern))) | |
return s | |
def shape(self): | |
"""Shape of the stitch block""" | |
return self.pattern.shape | |
def rows(self): | |
"""Number of rows in the stitch block""" | |
return self.pattern.shape[0] | |
def cols(self): | |
"""Number of columns in the stitch block""" | |
return self.pattern.shape[1] | |
def preview(self, yarn_colors): | |
"""Return a RGB image of a pattern, using | |
the color mappings in yarn_colors (numbers:symbolic names) | |
as the colors""" | |
r,c = self.pattern.shape | |
img = np.zeros((r,c,3)) | |
for ix, color_name in yarn_colors.items(): | |
img[self.pattern==ix,:] = colors.get(color_name, (1.,1.,1.)) | |
img[self.pattern==-1,:] = (1,1,1) | |
img[self.pattern==-2,:] = (1,0,1) | |
return img | |
def preview_adj(self, yarn_colors): | |
r,c = self.pattern.shape | |
img = np.zeros((r,c,3)) | |
x, y = 0,0 | |
increments = np.ones((c,)) | |
print("start", np.sum(increments)) | |
for row in self.pattern: | |
x = 0 | |
j =0 | |
while x<c: | |
col = row[x] | |
if col>=0: | |
color = colors[yarn_colors[col]] | |
img[y,x] = color | |
elif col==-1: | |
increments[j] += 1 | |
elif col==-2: | |
increments[j] -= 1 | |
x += increments[j] | |
j += 1 | |
y += 1 | |
print("end",c-(np.sum(increments)-c)) | |
return img | |
def valid_fairisle(self): | |
"""Return true if a pattern has at most | |
two colors per row (true Fair Isle)""" | |
for row in self.pattern: | |
row = row[row>0] # ignore decreases/increases | |
if len(np.unique(row))>2: | |
return False | |
return True | |
def copy(self): | |
"""Copy a stitch block""" | |
s = Stitches(np.array(self.pattern)) | |
return s | |
def trim(self, slice): | |
"""slice a pattern (convenience for [])""" | |
return Stitches(self.pattern[slice]) | |
def sym(self, pattern): | |
"""Return a symmetric variation of a pattern. | |
Styles: | |
v: stacked vertically flipped | |
| | |
-> | |
-> | |
| | |
h: stacked horizontally flipped | |
| | | |
-> <- | |
q: four way flip | |
| | | |
-> <- | |
-> <- | |
| | | |
qr: four way rotated 90 degrees | |
- | | |
| - | |
qrv: four way rotated 90 degrees (opposite dir) | |
| - | |
- | | |
hq: horizontal stack w/rotated 90 | |
->| | |
V | |
vq: vertical stack w/rotated 90 | |
-> | |
| | |
V | |
hr: horizontal stack w/rotated 180 | |
| | |
-><- | |
| | |
vr: vertical stack w/rotated 180 | |
| | |
-> | |
<- | |
| | |
""" | |
if pattern=='v': | |
return self.copy() & self.flipud() | |
elif pattern=='h': | |
return self.copy() + self.fliplr() | |
elif pattern=='q': | |
top = self.copy()+self.fliplr() | |
return top & top.flipud() | |
elif pattern=='qr': | |
a = self.copy() | |
b = a.rot90() | |
c = b.rot90() | |
d = c.rot90() | |
return (a+b) & (d+c) | |
elif pattern=='qrv': | |
a = self.copy() | |
b = a.rot90() | |
c = b.rot90() | |
d = c.rot90() | |
return (a+d) & (b+c) | |
elif pattern=='hq': | |
a = self.copy() | |
b = a.rot90() | |
return (a+b) | |
elif pattern=='vq': | |
a = self.copy() | |
b = a.rot90() | |
return (a&b) | |
elif pattern=='hr': | |
a = self.copy() | |
b = a.rot90().rot90() | |
return (a+b) | |
elif pattern=='vr': | |
a = self.copy() | |
b = a.rot90().rot90() | |
return (a&b) | |
def over(self, other): | |
"""overlay (or) a (binary) pattern with another pattern""" | |
return Stitches(self.pattern | other.pattern) | |
def mix(self, other): | |
"""mix (xor) a (binary) pattern from another pattern""" | |
return Stitches(self.pattern ^ other.pattern) | |
def intersect(self, other): | |
"""intersect a (binary) pattern from another pattern""" | |
return Stitches(self.pattern & other.pattern) | |
def diff(self, other): | |
"""subtract a (binary) pattern from another pattern""" | |
return Stitches(self.pattern & (~other.pattern)) | |
def pad(self, rows=(0,0), cols=(0,0), color=-1): | |
"""Pad the stitch block, in the given color. Each tuple | |
specifies the padding to the left(top) and right(bottom) | |
of the stitch block""" | |
return Stitches(np.pad(self.pattern, (rows, cols), mode='constant', constant_values=color)) | |
def __getitem__(self, slice): | |
"""Slice a stitch block""" | |
return Stitches(self.pattern.__getitem__(slice)) | |
def recolor_rows(self, rowlist): | |
"""Take a list of tuples, applying each tuple | |
as the color pair for each row in this stitch block""" | |
copy = self.copy() | |
for ix in range(copy.shape()[0]): | |
a, b = rowlist[ix] | |
row = copy.pattern[ix,:] | |
zeros = row==0 | |
ones = row!=0 | |
row[zeros] = a | |
row[ones] = b | |
copy.pattern[ix,:] = row | |
return copy | |
def pattern_string(self, colors): | |
out = "" | |
for ix,row in enumerate(self.pattern): | |
uniq = np.unique(row) | |
if len(uniq)==1: | |
uniq = (uniq[0], uniq[0]) | |
out += "Row %03d\t" % ix | |
out += "%s\t%s\t" % (colors[uniq[0]], colors[uniq[1]]) | |
out += "".join([colors[uniq[x]][0] for x in np.where(row==uniq[0],0,1)]) | |
out += "\n" | |
return out | |
def cyclic_v(self, n): | |
"""Cyclic shift the pattern n places to the bottom (n>0) | |
or top (n<0)""" | |
if n<0: | |
n = self.rows() - n | |
up = self.pattern[:n,:] | |
down = self.pattern[n:,:] | |
return Stitches(np.vstack([down, up])) | |
def cyclic_h(self, n): | |
"""Cyclic shift the pattern n places to the right (if n>0) | |
or left (n<0)""" | |
if n<0: | |
n = self.rows() - n | |
up = self.pattern[:,:n] | |
down = self.pattern[:,n:] | |
return Stitches(np.hstack([down, up])) | |
def blank(self, color=0): | |
"""Return stitch block of same size, but with | |
uniform color.""" | |
return Stitches(np.ones_like(self.pattern)*color) | |
def plain(color=0, rows=1, cols=1): | |
return Stitches(np.ones((rows,cols))*color) | |
zag = Stitches(""" | |
--=--- 10 | |
-=-=-- 10 | |
=---=- 20""") | |
tri = Stitches(""" | |
----=---- 21 | |
---===--- 21 | |
--=====-- 31 | |
""") | |
square = Stitches(""" | |
------ | |
--===- | |
--=-=- 32 | |
--===- | |
------ | |
""") | |
osquare = Stitches(""" | |
------ 32 | |
-===-- 32 | |
-=-=-- 12 | |
-===-- 32 | |
------ 32 | |
""") | |
cross = Stitches(""" | |
=-= 32 | |
-=- 01 | |
=-= 31 | |
-=- 01 | |
=-= 32 | |
""") | |
cross2 = Stitches(""" | |
-=-=-= 01 | |
-=---- 01 | |
=-=--- 21 | |
-=---- 01 | |
""") | |
chevron = Stitches(""" | |
==---- 32 | |
-==--- 21 | |
--==-- 31 | |
---==- 01 | |
----== 32 | |
""") | |
diamond = Stitches(""" | |
----=---- 23 | |
---===--- | |
--=====-- | |
-===-===- 21 | |
--=====-- | |
---===--- | |
----=---- 23 | |
""") | |
ex = Stitches(""" | |
=----- | |
-=-=-- | |
--=--- | |
-=-=-- | |
=----- | |
""") | |
ssquare = Stitches(""" | |
-------- 21 | |
-==----- 31 | |
=-=----- 31 | |
===----- 31 | |
-------- 11 | |
""") | |
""" | |
x = x + x | |
x = x * 3 | |
x = x & x.fliplr().recolor([ (1,2), (2,1)]) | |
x = x & x.plain(1) | |
x= x | y.recolor([(1,2), (0,1)]) | |
x =x & x.flipud().cyclic_h(4) | |
""" | |
tri = tri | |
x = chevron.sym("q") | cross | cross.plain(1) | diamond.recolor([(0,3)]) | cross | cross.plain(1) | zag.flipud() | tri.flipud() | ( osquare )| tri | zag | ex.plain(1) | ex | cross.plain(1).recolor([(0,1)]) | diamond.cyclic_h(4) | cross.plain(1).recolor([(0,1)]) | cross.flipud() | osquare | chevron | chevron.fliplr() | |
x = chevron.flipud() | plain() | diamond.recolor([(0,3),(1,2), (2,1)]) | plain(color=2) | cross | plain(color=3) | zag.flipud() | tri.flipud() | ( osquare )| tri | zag.recolor([(0,3)]) | plain(color=3) | cross2.flipud() | plain(color=2) | chevron.recolor([[0,3]]) | |
preview = (x).preview({0:"eucalyptus", 1:"caspian", | |
2:"petrol", 3:"nighthawk"}) | |
plt.imshow(preview, interpolation='nearest') | |
plt.savefig("fairisle4.png") | |
#with open("fairisle2.txt", "w") as f: | |
# f.write(x.pattern_string({0:"eucalyptus", 1:"caspian", | |
#2:"petrol", 3:"nighthawk"})) | |
plt.show() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment