|
|
|
from util_v0 import * |
|
from pytorch_v0 import * |
|
|
|
try: |
|
import igl |
|
import meshplot as mp |
|
except: |
|
pass |
|
|
|
try: |
|
import skimage |
|
from skimage import measure as _ |
|
from skimage import color as _ |
|
from skimage import segmentation as _ |
|
from skimage import filters as _ |
|
from scipy.spatial.transform import Rotation |
|
except: |
|
pass |
|
|
|
try: |
|
import colorsys |
|
except: |
|
pass |
|
|
|
try: |
|
import imagesize |
|
except: |
|
pass |
|
|
|
|
|
|
|
|
|
def img2uri(img): |
|
bio = io.BytesIO() |
|
img.save(bio, 'PNG') |
|
return base64.b64encode(bio.getvalue()) |
|
|
|
def uri2img(uri): |
|
return Image.open(io.BytesIO(base64.b64decode(uri))) |
|
|
|
|
|
|
|
|
|
|
|
class I: |
|
|
|
def __init__(self, data): |
|
|
|
if isinstance(data, str): |
|
data = Image.open(data) |
|
elif isinstance(data, bytes): |
|
data = uri2img(data) |
|
self.data = data |
|
|
|
|
|
if isinstance(self.data, Image.Image): |
|
|
|
self.dtype = 'pil' |
|
self.mode = self.data.mode |
|
self.shape = ( |
|
len(self.data.getbands()), |
|
self.data.size[1], |
|
self.data.size[0], |
|
) |
|
self.size = self.shape[1:] |
|
elif isinstance(self.data, np.ndarray): |
|
|
|
if len(self.data.shape)==2: |
|
self.data = self.data[None,] |
|
elif len(self.data.shape)==4: |
|
assert self.data.shape[0]==1 |
|
self.data = self.data[0] |
|
if self.data.shape[0] not in [1,3,4]: |
|
self.data = self.data.transpose(2,0,1) |
|
if np.issubdtype(self.data.dtype, np.floating): |
|
pass |
|
elif self.data.dtype==np.bool: |
|
self.data = self.data.astype(np.float) |
|
elif np.issubdtype(self.data.dtype, np.integer): |
|
self.data = self.data.astype(np.float) / 255.0 |
|
self.dtype = 'np' |
|
self.mode = { |
|
1: 'L', |
|
3: 'RGB', |
|
4: 'RGBA', |
|
}[self.data.shape[0]] |
|
self.shape = self.data.shape |
|
self.size = self.shape[1:] |
|
elif isinstance(self.data, torch.Tensor): |
|
|
|
|
|
if len(self.data.shape)==2: |
|
self.data = self.data[None,] |
|
elif len(self.data.shape)==4: |
|
assert self.data.shape[0]==1 |
|
self.data = self.data[0] |
|
if self.data.shape[0] not in [1,3,4]: |
|
self.data = self.data.permute(2,0,1) |
|
self.dtype = 'torch' |
|
self.mode = { |
|
1: 'L', |
|
3: 'RGB', |
|
4: 'RGBA', |
|
}[self.data.shape[0]] |
|
self.shape = tuple(self.data.shape) |
|
self.size = self.shape[1:] |
|
elif isinstance(self.data, I): |
|
self.dtype = self.data.dtype |
|
self.mode = self.data.mode |
|
self.shape = self.data.shape |
|
self.size = self.data.size |
|
self.data = self.data.data |
|
elif isinstance(self.data, plt.Figure): |
|
buff = io.BytesIO() |
|
self.data.savefig(buff) |
|
self.__init__(Image.open(buff)) |
|
else: |
|
assert 0, 'data not understood' |
|
self.diam = diam(self.size) |
|
return |
|
|
|
|
|
def convert(self, mode): |
|
return I(self.pil(mode=mode)) |
|
def invert(self, invert_alpha=False): |
|
data = self.np() |
|
if self.mode=='RGBA' and not invert_alpha: |
|
return I(np.concatenate([ |
|
1-data[:3], data[3:], |
|
])) |
|
else: |
|
return I(1-self.np()) |
|
def pil(self, mode=None): |
|
if self.dtype=='pil': |
|
ans = self.data |
|
elif self.dtype=='np': |
|
data = 255*self.data.clip(0,1) |
|
ans = Image.fromarray(( |
|
data.transpose(1,2,0) if data.shape[0]!=1 |
|
else data[0] |
|
).astype(np.uint8)) |
|
elif self.dtype=='torch': |
|
ans = F.to_pil_image(self.data.float().clamp(0,1).cpu()) |
|
else: |
|
assert 0, 'data not understood' |
|
return ans if mode is None else ans.convert(mode) |
|
def p(self, *args, **kwargs): |
|
return self.pil(*args, **kwargs) |
|
def pimg(self, mode=None): |
|
return self.pil(mode=mode) |
|
def np(self): |
|
if self.dtype=='pil': |
|
return I(np.asarray(self.data)).data |
|
elif self.dtype=='np': |
|
return self.data |
|
elif self.dtype=='torch': |
|
return self.data.cpu().numpy() |
|
assert 0, 'data not understood' |
|
def n(self): |
|
return self.np() |
|
def numpy(self): |
|
return self.np() |
|
def nimg(self): |
|
return self.np() |
|
def uint8(self, ch_last=True): |
|
ans = (self.np()*255).astype(np.uint8) |
|
return ans.transpose(1,2,0) if ch_last else ans |
|
def cv2(self): |
|
return self.uint8(ch_last=True)[...,::-1] |
|
def bgr(self): |
|
x = self.np() |
|
if self.mode=='RGBA': |
|
return I(x[[2,1,0,3]]) |
|
else: |
|
return I(x[::-1]) |
|
def tensor(self): |
|
if self.dtype=='pil': |
|
return F.to_tensor(self.data) |
|
elif self.dtype=='np': |
|
return torch.from_numpy(self.data.copy()) |
|
elif self.dtype=='torch': |
|
return self.data |
|
assert 0, 'data not understood' |
|
def t(self): |
|
return self.tensor() |
|
def torch(self): |
|
return self.tensor() |
|
def timg(self): |
|
return self.tensor() |
|
def save(self, fn): |
|
self.pil().save(fn) |
|
return fn |
|
|
|
|
|
def rescale(self, factor, resample='bilinear', antialias=False): |
|
return self.resize( |
|
rescale_dry(self.size, factor), |
|
resample=resample, antialias=antialias, |
|
) |
|
def resize(self, s, resample='bilinear', antialias=False): |
|
s = pixel_ij(s, rounding=True) |
|
if self.dtype=='pil': |
|
return I(self.data.resize( |
|
s[::-1], resample=getattr(Image, resample.upper()), |
|
)) |
|
elif self.dtype=='np': |
|
return I(self.pil()).resize(s, resample=resample) |
|
elif self.dtype=='torch': |
|
return I(F.resize( |
|
self.data, |
|
s, |
|
interpolation=getattr(F.InterpolationMode, resample.upper()), |
|
|
|
)) |
|
|
|
assert 0, 'data not understood' |
|
def resize_w(self, s=512, resample='bilinear', antialias=False): |
|
h,w = self.size |
|
return self.resize((h*s/w, s)) |
|
def resize_h(self, s=512, resample='bilinear', antialias=False): |
|
h,w = self.size |
|
return self.resize((s, w*s/h)) |
|
def resize_max(self, s=512, resample='bilinear', antialias=False): |
|
dry = resize_max_dry(self.size, s=s) |
|
return self.resize(dry, resample=resample) |
|
def resize_min(self, s=512, resample='bilinear', antialias=False): |
|
dry = resize_min_dry(self.size, s=s) |
|
return self.resize(dry, resample=resample) |
|
def resize_square( |
|
self, s=512, |
|
resample='bilinear', antialias=False, |
|
fill=0, padding_mode='constant', |
|
): |
|
res = self.resize_max(s=s, resample=resample, antialias=antialias).tensor() |
|
dry = resize_square_dry(res, s=s) |
|
fc = dry[0] |
|
pad = F.pad( |
|
res, |
|
padding=[ |
|
-fc[1], |
|
-fc[0], |
|
s+fc[1]-res.shape[2], |
|
s+fc[0]-res.shape[1], |
|
], |
|
fill=fill, padding_mode=padding_mode, |
|
) |
|
return I(pad) |
|
|
|
def rw(self, *args, **kwargs): |
|
return self.resize_w(*args, **kwargs) |
|
def rh(self, *args, **kwargs): |
|
return self.resize_h(*args, **kwargs) |
|
def rmax(self, s=512, resample='bilinear', antialias=False): |
|
return self.resize_max(s=s, resample=resample, antialias=antialias) |
|
def rmin(self, s=512, resample='bilinear', antialias=False): |
|
return self.resize_min(s=s, resample=resample, antialias=antialias) |
|
def rsqr(self, *args, **kwargs): |
|
return self.resize_square(*args, **kwargs) |
|
|
|
|
|
def transpose(self): |
|
if self.dtype=='pil': |
|
return I(self.data.transpose(method=Image.TRANSPOSE)) |
|
elif self.dtype=='np': |
|
return I(np.swapaxes(self.data, 1, 2)) |
|
elif self.dtype=='torch': |
|
return I(self.data.permute(0,2,1)) |
|
assert 0, 'data not understood' |
|
def T(self): |
|
return self.transpose() |
|
def fliph(self): |
|
if self.dtype=='pil': |
|
return I(self.data.transpose(method=Image.FLIP_LEFT_RIGHT)) |
|
elif self.dtype=='np': |
|
return I(self.data[...,::-1]) |
|
elif self.dtype=='torch': |
|
return I(self.data.flip(dims=(2,))) |
|
assert 0, 'data not understood' |
|
def flipv(self): |
|
if self.dtype=='pil': |
|
return I(self.data.transpose(method=Image.FLIP_TOP_BOTTOM)) |
|
elif self.dtype=='np': |
|
return I(self.data[:,::-1]) |
|
elif self.dtype=='torch': |
|
return I(self.data.flip(dims=(1,))) |
|
assert 0, 'data not understood' |
|
def rotate(self, deg): |
|
if deg==0: |
|
return self |
|
elif deg==90: |
|
return self.rotate90() |
|
elif deg==180: |
|
return self.rotate180() |
|
elif deg==270: |
|
return self.rotate270() |
|
elif deg==360: |
|
return self |
|
assert 0, 'data not understood' |
|
def rotate90(self): |
|
if self.dtype=='pil': |
|
return I(self.data.transpose(method=Image.ROTATE_90)) |
|
elif self.dtype in ['np', 'torch']: |
|
return self.transpose().flipv() |
|
def rotate180(self): |
|
if self.dtype=='pil': |
|
return I(self.data.transpose(method=Image.ROTATE_180)) |
|
elif self.dtype in ['np', 'torch']: |
|
return self.fliph().flipv() |
|
def rotate270(self): |
|
if self.dtype=='pil': |
|
return I(self.data.transpose(method=Image.ROTATE_270)) |
|
elif self.dtype in ['np', 'torch']: |
|
return self.transpose().fliph() |
|
|
|
|
|
def cropbox(self, from_corner, from_size, to_size=None, resample='bilinear'): |
|
from_corner = pixel_ij(from_corner, rounding=True) |
|
from_size = pixel_ij(from_size, rounding=True) |
|
to_size = pixel_ij(to_size, rounding=True) if to_size!=None else from_size |
|
return I(F.resized_crop( |
|
self.pil().convert('RGBA'), |
|
from_corner[0], |
|
from_corner[1], |
|
from_size[0], |
|
from_size[1], |
|
to_size, |
|
interpolation=getattr(F.InterpolationMode, resample.upper()), |
|
)) |
|
def cb(self, *args, **kwargs): |
|
return self.cropbox(*args, **kwargs) |
|
|
|
|
|
def alpha_composite(self, img, opacity=1.0): |
|
a = self.pil().convert('RGBA') |
|
b = I(img).pil().convert('RGBA') |
|
if opacity==0: |
|
return I(a) |
|
|
|
|
|
else: |
|
b = I(b).np() * np.asarray([1,1,1,opacity])[:,None,None] |
|
b = I(b).pil() |
|
return I(Image.alpha_composite(a,b)) |
|
def alpha_bg(self, c=0.5): |
|
return iblank(self.size, c=c).alpha_composite(self, opacity=1.0) |
|
def alpha_bbox(self, thresh=0.5): |
|
return alpha_bbox(self, thresh=thresh) |
|
def as_alpha(self, c=0): |
|
return iblank(self.size, c=c).alpha(self) |
|
def alpha(self, a=1.0): |
|
|
|
rgba = I(self.pil().convert('RGBA')) |
|
a = I(a*np.ones(self.size)) if type(a) in [float, int] else I(a) |
|
return I(np.concatenate([ |
|
rgba.numpy()[:-1], a.numpy()[-1:], |
|
])) |
|
|
|
def acomp(self, *args, **kwargs): |
|
return self.alpha_composite(*args, **kwargs) |
|
def abg(self, *args, **kwargs): |
|
return self.alpha_bg(*args, **kwargs) |
|
def abbox(self, *args, **kwargs): |
|
return self.alpha_bbox(*args, **kwargs) |
|
def aa(self, *args, **kwargs): |
|
return self.as_alpha(*args, **kwargs) |
|
|
|
|
|
def left(self, img, bg='k'): |
|
return igrid([img, self], bg=bg) |
|
def right(self, img, bg='k'): |
|
return igrid([self, img], bg=bg) |
|
def top(self, img, bg='k'): |
|
return igrid([[img,], [self,]], bg=bg) |
|
def bottom(self, img, bg='k'): |
|
return igrid([[self,], [img,]], bg=bg) |
|
|
|
|
|
def rect(self, corner, size, w=1, c='r', f=None): |
|
corner = pixel_ij(corner, rounding=True) |
|
size = pixel_ij(size, rounding=True) |
|
w = max(1, round(w)) |
|
c = c255(c) |
|
f = c255(f) |
|
ans = self.pil(mode='RGBA').copy() |
|
d = ImageDraw.Draw(ans) |
|
d.rectangle( |
|
[corner[1], corner[0], corner[1]+size[1]-1, corner[0]+size[0]-1], |
|
fill=f, outline=c, width=w, |
|
) |
|
return I(ans) |
|
def bbox(self, *args, **kwargs): |
|
return self.rect(*args, **kwargs) |
|
def border(self, w=1, c='r'): |
|
return self.rect( |
|
(0, 0), |
|
self.size, |
|
w=w, c=c, f=None, |
|
) |
|
def dot(self, point, s=1, c='r'): |
|
c = c255(c) |
|
x,y = pixel_ij(point, rounding=False) |
|
ans = self.pil(mode='RGBA').copy() |
|
d = ImageDraw.Draw(ans) |
|
d.ellipse( |
|
[(y-s,x-s), (y+s,x+s)], |
|
fill=c, |
|
) |
|
return I(ans) |
|
def point(self, *args, **kwargs): |
|
return self.dot(*args, **kwargs) |
|
def line(self, a, b, w=1, c='r'): |
|
a = pixel_ij(a, rounding=False) |
|
b = pixel_ij(b, rounding=False) |
|
c = c255(c) |
|
w = max(1, round(w)) |
|
ans = self.pil(mode='RGBA').copy() |
|
d = ImageDraw.Draw(ans) |
|
d.line([a[::-1], (b[1]-1,b[0]-1)], fill=c, width=w) |
|
return I(ans) |
|
|
|
|
|
def text(self, text, pos, s=12, anchor='tl', c='m', bg='k', spacing=None, padding=0): |
|
t = itext( |
|
text, s=s, c=c, bg=bg, |
|
spacing=spacing, padding=padding, |
|
) |
|
x,y = pos |
|
x = { |
|
't': x, |
|
'b': x-t.size[0], |
|
'c': x-t.size[0]/2, |
|
}[anchor[0].lower()] |
|
y = { |
|
'l': y, |
|
'r': y-t.size[1], |
|
'c': y-t.size[1]/2, |
|
}[anchor[1].lower()] |
|
t = t.pil('RGBA') |
|
ans = self.pil('RGBA') |
|
ans.paste(t, pixel_ij((y,x), rounding=True), t) |
|
return I(ans) |
|
def caption(self, text, s=24, pos='t', c='w', bg='k', spacing=None, padding=None): |
|
pos = pos[0].lower() |
|
t = itext(text, s=s, c=c, bg=bg, spacing=spacing, padding=padding) |
|
if pos=='t': |
|
return self.top(t) |
|
elif pos=='b': |
|
return self.bottom(t) |
|
elif pos=='l': |
|
return self.left(t) |
|
elif pos=='r': |
|
return self.right(t) |
|
assert 0, 'data not understood' |
|
def cap(self, *args, **kwargs): |
|
return self.caption(*args, **kwargs) |
|
|
|
|
|
def _repr_png_(self): |
|
bio = io.BytesIO() |
|
self.pil().save(bio, 'PNG') |
|
return bio.getvalue() |
|
|
|
|
|
|
|
def pimg(x): |
|
return I(x).pil() |
|
def nimg(x): |
|
return I(x).numpy() |
|
def timg(x): |
|
return I(x).tensor() |
|
|
|
|
|
def pixel_rounder(n, mode): |
|
if mode==True or mode=='round': |
|
return round(n) |
|
elif mode=='ceil': |
|
return math.ceil(n) |
|
elif mode=='floor': |
|
return math.floor(n) |
|
else: |
|
return n |
|
def pixel_ij(x, rounding=True): |
|
if isinstance(x, np.ndarray): |
|
x = x.tolist() |
|
return tuple(pixel_rounder(i, rounding) for i in ( |
|
x if isinstance(x, tuple) or isinstance(x, list) else (x,x) |
|
)) |
|
def diam(x): |
|
if isinstance(x, tuple) or isinstance(x, list): |
|
h,w = x[-2:] |
|
elif isinstance(x, I): |
|
h,w = x.size |
|
else: |
|
h,w = x.shape[-2:] |
|
return np.sqrt(h**2 + w**2) |
|
def rescale(x, factor, resample='bilinear', antialias=False): |
|
return x.rescale(factor, resample=resample, antialias=antialias) |
|
def rescale_dry(x, factor): |
|
h,w = x[-2:] if isinstance(x, tuple) or isinstance(x, list) else I(x).size |
|
return (h*factor, w*factor) |
|
def resize_max(x, s=512, resample='bilinear', antialias=False): |
|
return I(x).resize_max(s=s, resample=resample, antialias=antialias) |
|
def resize_max_dry(x, s=512): |
|
|
|
h,w = x[-2:] if isinstance(x, tuple) or isinstance(x, list) else I(x).size |
|
return ( |
|
(s, int(w*s/h)), |
|
(int(h*s/w), s), |
|
)[h<w] |
|
def resize_min(x, s=512, resample='bilinear', antialias=False): |
|
return I(x).resize_min(s=s, resample=resample, antialias=antialias) |
|
def resize_min_dry(x, s=512): |
|
|
|
h,w = x[-2:] if isinstance(x, tuple) or isinstance(x, list) else I(x).size |
|
return ( |
|
(s, int(w*s/h)), |
|
(int(h*s/w), s), |
|
)[w<h] |
|
def resize_square( |
|
x, s=512, |
|
resample='bilinear', antialias=False, |
|
fill=0, padding_mode='constant', |
|
): |
|
return I(x).resize_square( |
|
s=s, resample=resample, antialias=antialias, |
|
fill=fill, padding_mode=padding_mode, |
|
) |
|
def resize_square_dry(x, s=512): |
|
|
|
h,w = x[-2:] if isinstance(x, tuple) or isinstance(x, list) else I(x).size |
|
from_corner = ( |
|
(0, -(h-w)//2), |
|
(-(w-h)//2, 0), |
|
)[h<w] |
|
from_size = (max(h,w),)*2 |
|
to_size = (s, s) |
|
return (from_corner, from_size, to_size) |
|
|
|
|
|
def cropbox(x, from_corner, from_size, to_size=None, resample='bilinear'): |
|
return I(x).cropbox( |
|
from_corner, from_size, to_size, resample=resample, |
|
) |
|
def cropbox_compose(cba, cbb): |
|
|
|
fca,fsa,tsa = [pixel_ij(q, rounding=False) for q in cba] |
|
fcb,fsb,tsb = [pixel_ij(q, rounding=False) for q in cbb] |
|
sfx = fsa[0] / tsa[0] |
|
sfy = fsa[1] / tsa[1] |
|
fc = fca[0]+fcb[0]*sfx, fca[1]+fcb[1]*sfy |
|
fs = fsb[0]*sfx, fsb[1]*sfy |
|
ts = tsb |
|
return fc, fs, ts |
|
def cropbox_sequence(cropboxes): |
|
|
|
ans = cropboxes[-1] |
|
for c in range(len(cropboxes)-2, -1, -1): |
|
cb = cropboxes[c] |
|
ans = cropbox_compose(cb, ans) |
|
return ans |
|
def cropbox_points(pts, from_corner, from_size, to_size): |
|
|
|
pts = np.asarray(pts) |
|
assert len(pts.shape)==2 and pts.shape[1]==2 |
|
fc = pixel_ij(from_corner, rounding=False) |
|
fs = pixel_ij(from_size, rounding=False) |
|
ts = pixel_ij(to_size, rounding=False) |
|
fc = np.asarray(fc)[None,] |
|
sf = np.asarray([ts[0]/fs[0], ts[1]/fs[1]])[None,] |
|
return (pts-fc)*sf |
|
def cropbox_bbox(bbox, from_corner, from_size, to_size): |
|
|
|
pts = [ |
|
bbox[0], |
|
(bbox[1][0]+bbox[0][0], bbox[1][1]+bbox[0][1]), |
|
] |
|
ans = cropbox_points(pts, from_corner, from_size, to_size) |
|
return [ |
|
(float(ans[0,0]), float(ans[0,1])), |
|
(float(ans[1,0]-ans[0,0]), float(ans[1,1]-ans[0,1])), |
|
] |
|
def cropbox_inverse(origin_size, from_corner, from_size, to_size): |
|
|
|
|
|
origin_size = pixel_ij(origin_size, rounding=False) |
|
from_corner = pixel_ij(from_corner, rounding=False) |
|
from_size = pixel_ij(from_size, rounding=False) |
|
to_size = pixel_ij(to_size, rounding=False) |
|
sx,sy = to_size[0]/from_size[0], to_size[1]/from_size[1] |
|
return [ |
|
(-from_corner[0]*sx, -from_corner[1]*sy), |
|
(origin_size[0]*sx, origin_size[1]*sy), |
|
origin_size, |
|
] |
|
def cropbox_bbox_square(bbox, s=512, padding=0): |
|
|
|
|
|
pd = padding |
|
fins = s + 2*padding |
|
return cropbox_sequence([ |
|
[bbox[0], bbox[1], bbox[1]], |
|
resize_square_dry(bbox[1], s=s), |
|
[(-pd,-pd), (fins,fins), (fins,fins)], |
|
]) |
|
def cropbox_borders(from_size, top_bottom, left_right): |
|
|
|
|
|
h,w = pixel_ij(from_size, rounding=False) |
|
t,b = pixel_ij(top_bottom, rounding=False) |
|
l,r = pixel_ij(left_right, rounding=False) |
|
return [ |
|
(t, l), |
|
(h-t-b, w-l-r), |
|
(h-t-b, w-l-r), |
|
] |
|
def cropbox_resize(from_size, to_size): |
|
|
|
return [ |
|
(0, 0), |
|
pixel_ij(from_size, rounding=False), |
|
pixel_ij(to_size, rounding=False), |
|
] |
|
def cropbox_to_mask(origin_size, from_corner, from_size, conservative=True): |
|
s = pixel_ij(origin_size, rounding=True) |
|
if conservative: |
|
|
|
x,y = pixel_ij(from_corner, rounding=False) |
|
h,w = pixel_ij(from_size, rounding=False) |
|
t,b = max(0, math.ceil(x)), min(math.floor(x+h), s[0]) |
|
l,r = max(0, math.ceil(y)), min(math.floor(y+w), s[1]) |
|
else: |
|
x,y = pixel_ij(from_corner, rounding=True) |
|
h,w = pixel_ij(from_size, rounding=True) |
|
t,b = max(0, x), min(x+h, s[0]) |
|
l,r = max(0, y), min(y+w, s[1]) |
|
ans = np.zeros(s) |
|
ans[t:b,l:r] = 1 |
|
return ans[None,] |
|
def bbox_lim(bbox, xlim=None, ylim=None, blim=None): |
|
|
|
if blim is not None: |
|
assert xlim is None and ylim is None |
|
(x,y),(h,w) = blim |
|
return bbox_lim(bbox, xlim=(x,x+h), ylim=(y,y+w)) |
|
|
|
|
|
else: |
|
assert xlim is not None or ylim is not None |
|
(a,b),(h,w) = bbox |
|
u,v = a+h, b+w |
|
if xlim is not None: |
|
if isinstance(xlim, tuple) or isinstance(xlim, list): |
|
x0,x1 = xlim |
|
else: |
|
x0 = x1 = xlim |
|
a = np.clip(a, a_min=x0, a_max=x1) |
|
u = np.clip(u, a_min=x0, a_max=x1) |
|
if ylim is not None: |
|
if isinstance(ylim, tuple) or isinstance(ylim, list): |
|
y0,y1 = ylim |
|
else: |
|
y0 = y1 = ylim |
|
b = np.clip(b, a_min=y0, a_max=y1) |
|
v = np.clip(v, a_min=y0, a_max=y1) |
|
return (a,b),(u-a,v-b) |
|
|
|
|
|
def c255(c): |
|
|
|
if c is None: |
|
return None |
|
if isinstance(c, str): |
|
c = { |
|
'r': (1,0,0), |
|
'g': (0,1,0), |
|
'b': (0,0,1), |
|
'k': 0, |
|
'w': 1, |
|
't': (0,1,1), |
|
'm': (1,0,1), |
|
'y': (1,1,0), |
|
'a': (0,0,0,0), |
|
}[c] |
|
if isinstance(c, list) or isinstance(c, tuple): |
|
if len(c)==3: |
|
c = c + (1,) |
|
elif len(c)==1: |
|
c = (c,c,c,1) |
|
c = tuple(int(255*q) for q in c) |
|
else: |
|
c = int(255*c) |
|
c = (c,c,c,255) |
|
return c |
|
def ucolors(num_colors): |
|
|
|
colors=[] |
|
for i in np.arange(0., 360., 360. / num_colors): |
|
hue = i/360. |
|
lightness = (50 + np.random.rand() * 10)/100. |
|
saturation = (90 + np.random.rand() * 10)/100. |
|
colors.append(colorsys.hls_to_rgb(hue, lightness, saturation)) |
|
return colors |
|
|
|
|
|
def iblank(size, c=(0,0,0,1)): |
|
size = pixel_ij(size, rounding=True) |
|
assert max(size)<4098*2 |
|
if c is None: c = 'a' |
|
c = c255(c) |
|
return I(Image.fromarray( |
|
np.asarray(c, dtype=np.uint8)[None,None] |
|
)).resize(size, resample='nearest') |
|
def alpha_composite(a, b, opacity=1.0): |
|
return I(a).alpha_composite(b, opacity=opacity) |
|
def alpha_bg(x, c=0.5): |
|
return I(x).alpha_bg(c=c) |
|
def alpha_bbox(img, thresh=0.5): |
|
h,w = img.size |
|
rgba = img.np() |
|
assert len(rgba) in [1,4] |
|
a = rgba[-1] |
|
x = np.max(a, axis=1)>thresh |
|
y = np.max(a, axis=0)>thresh |
|
whx = np.where(x)[0] |
|
why = np.where(y)[0] |
|
x0,x1 = (whx.min(),whx.max()+1) if len(whx)>0 else (0,h) |
|
y0,y1 = (why.min(),why.max()+1) if len(why)>0 else (0,w) |
|
fc = x0, y0 |
|
s = x1-x0, y1-y0 |
|
return fc, s |
|
def igrid(imgs, just=True, bg='k'): |
|
|
|
assert isinstance(imgs, list) |
|
if any(isinstance(i, list) for i in imgs): |
|
x = [ |
|
[j for j in i] if isinstance(i, list) else [i,] |
|
for i in imgs |
|
] |
|
else: |
|
x = [[i for i in imgs],] |
|
|
|
|
|
nrows = len(x) |
|
ncols = max(len(row) for row in x) |
|
hs = np.zeros((nrows,ncols)) |
|
ws = np.zeros((nrows,ncols)) |
|
for r in range(nrows): |
|
row = x[r] |
|
for c in range(ncols): |
|
if c==len(row): row.append(None) |
|
item = row[c] |
|
if item is None: |
|
hs[r,c] = ws[r,c] = 0 |
|
else: |
|
item = I(item) |
|
hs[r,c], ws[r,c] = item.size |
|
row[c] = item |
|
offx = np.cumsum(np.max(hs, axis=1)) |
|
if just: |
|
offy = np.cumsum(np.max(ws, axis=0))[None,...].repeat(nrows,0) |
|
else: |
|
offy = np.cumsum(ws, axis=1) |
|
|
|
|
|
ans = Image.new('RGBA', (int(offy.max()), int(offx[-1]))) |
|
for r in range(nrows): |
|
for c in range(ncols): |
|
item = x[r][c] |
|
if item is not None: |
|
ox = offx[r-1] if r>0 else 0 |
|
oy = offy[r,c-1] if c>0 else 0 |
|
ans.paste(item.pil(mode='RGBA'), (int(oy),int(ox))) |
|
ans = I(ans).alpha_bg(bg) |
|
return ans |
|
|
|
|
|
FN_ARIAL = './env/arial_monospaced_mt.ttf' |
|
if not os.path.isfile(FN_ARIAL): |
|
FN_ARIAL = './__env__/arial_monospaced_mt.ttf' |
|
def itext( |
|
text, |
|
s=24, |
|
facing='right', |
|
pos='left', |
|
c='w', |
|
bg='k', |
|
h=None, |
|
w=None, |
|
spacing=None, |
|
padding=None, |
|
force_size=False, |
|
): |
|
|
|
text = str(text) |
|
s = max(1, round(s)) |
|
spacing = math.ceil(s*4/10) if spacing is None else spacing |
|
padding = math.ceil(s*4/10) if padding is None else padding |
|
facing = facing.lower() |
|
if facing in ['u', 'up', 'd', 'down']: |
|
h,w = w,h |
|
c,bg = c255(c), c255(bg) |
|
f = PIL.ImageFont.truetype(FN_ARIAL, s) |
|
|
|
td = PIL.ImageDraw.Draw(Image.new('RGBA', (1,1), (0,0,0,0))) |
|
tw,th = td.multiline_textsize(text, font=f, spacing=spacing) |
|
if not force_size: |
|
if h and h<th: h = th |
|
if w and w<tw: w = tw |
|
h = h or th+2*padding |
|
w = w or tw+2*padding |
|
|
|
pos = pos.lower() |
|
an = None |
|
if pos in ['c', 'center']: |
|
xy = (w//2, h//2) |
|
an = 'mm' |
|
align = 'center' |
|
elif pos in ['l', 'lc', 'cl', 'left']: |
|
xy = (padding, h//2) |
|
an = 'lm' |
|
align = 'left' |
|
elif pos in ['r', 'rc', 'cr', 'right']: |
|
xy = (w-padding, h//2) |
|
an = 'rm' |
|
align = 'right' |
|
elif pos in ['t', 'tc', 'ct', 'top']: |
|
xy = (w//2, padding) |
|
an = 'ma' |
|
align = 'center' |
|
elif pos in ['b', 'bc', 'cb', 'bottom']: |
|
xy = (w//2, h-padding) |
|
an = 'md' |
|
align = 'center' |
|
elif pos in ['tl', 'lt']: |
|
xy = (padding, padding) |
|
align = 'left' |
|
elif pos in ['bl', 'lb']: |
|
xy = (padding, h-padding-th) |
|
align = 'left' |
|
elif pos in ['tr', 'rt']: |
|
xy = (w-padding-tw, padding) |
|
align = 'right' |
|
elif pos in ['br', 'rb']: |
|
xy = (w-padding-tw, h-padding-th) |
|
align = 'right' |
|
else: |
|
assert False, 'pos not understood' |
|
|
|
ans = Image.new('RGBA', (w,h), bg) |
|
d = PIL.ImageDraw.Draw(ans) |
|
d.multiline_text( |
|
xy, text, fill=c, font=f, anchor=an, |
|
spacing=spacing, align=align, |
|
) |
|
|
|
if facing in ['l', 'left']: |
|
ans = ans.rotate(180) |
|
elif facing in ['u', 'up']: |
|
ans = ans.rotate(90, expand=True) |
|
elif facing in ['d', 'down']: |
|
ans = ans.rotate(-90, expand=True) |
|
return I(ans) |
|
|
|
|
|
|
|
|
|
def pixel_logit(x, pixel_margin=1): |
|
x = (x*(255-2*pixel_margin) + pixel_margin) / 255 |
|
return torch.log(x / (1-x)) |
|
|
|
|
|
|
|
|