Skip to content

Instantly share code, notes, and snippets.

@johnhw
Created July 8, 2018 19:50
Show Gist options
  • Save johnhw/ac6c788b36454aecdf0135e978c92e4b to your computer and use it in GitHub Desktop.
Save johnhw/ac6c788b36454aecdf0135e978c92e4b to your computer and use it in GitHub Desktop.
stitches.py
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