text: 
  #################################################
  ### THIS FILE WAS AUTOGENERATED! DO NOT EDIT! ###
  #################################################
  # file to edit: dev_nb/10_augmentation.ipynb
  
  from exp.nb_09c import *
  
  make_rgb._order = 0
  
  import random
  
  def show_image(im, ax=None, figsize=(3,3)):
      if ax is None: _,ax = plt.subplots(1,1, figsize=figsize)
      ax.axis('off')
      ax.imshow(im.permute(1,2,0))
  
  def show_batch(x, c=4, r=None, figsize=None):
      n = len(x)
      if r is None: r = int(math.ceil(n/c))
      if figsize is None: figsize=(c*3,r*3)
      fig, axes = plt.subplots(r,c, figsize=figsize)
      for xi,ax in zip(x, axes.flat): show_image(xi, ax)
  
  class PilTransform(Transform): _order = 11
  
  class PilRandomFlip(PilTransform):
      def __init__(self, p=0.5): self.p = p
      def __call__(self, x): return x.transpose(PIL.Image.FLIP_LEFT_RIGHT) if random.random() < self.p else x
  
  class PILRandomDihedral(PilTransform):
      def __init__(self, p=0.75): self.p = p * 7/8
      def __call__(self, x):
          if random.random() > self.p: return x
          return x.transpose(random.randint(0,6))
  
  from random import randint
  
  def process_sz(sz):
      sz = listify(sz)
      return tuple(sz if len(sz)==2 else [sz[0], sz[0]])
  
  def default_crop_size(w,h): return [w,w] if w < h else [h,h]
  
  class GeneralCrop(PilTransform):
      def __init__(self, size, crop_size=None, resample=PIL.Image.BILINEAR):
          self.resample,self.size = resample,process_sz(size)
          self.crop_size = None if crop_size is None else process_sz(crop_size)
  
      def default_crop_size(self, w,h): return default_crop_size(w,h)
      def get_corners(self, w, h, wc, hc): return (0,0,w,h) # full image
  
      def __call__(self, x):
          csize = self.default_crop_size(*x.size) if self.crop_size is None else self.crop_size
          return x.transform(self.size, PIL.Image.EXTENT, self.get_corners(*x.size, *csize), resample=self.resample)
  
  
  class CenterCrop(GeneralCrop):
      def __init__(self, size, scale=1.14, resample=PIL.Image.BILINEAR):
          super().__init__(size, resample=resample)
          self.scale = scale
  
      def default_crop_size(self, w, h): return [w/self.scale, h/self.scale]
  
      def get_corners(self, w, h, wc, hc):
          return ((w-wc)//2, (h-hc)//2, (w-wc)//2+wc, (h-hc)//2+hc)
  
  class RandomResizedCrop(GeneralCrop):
      def __init__(self, size, scale=(0.08,1.0), ratio=(3./4., 4./3.), resample=PIL.Image.BILINEAR):
          super().__init__(size, resample=resample)
          self.scale,self.ratio = scale,ratio
  
      def get_corners(self, w, h, wc, hc):
          area = w * h
          # Tries 10 times to get a proper crop inside the image
          for attempt in range(10):
              area = random.uniform(*self.scale) * area
              ratio = math.exp(random.uniform(math.log(self.ratio[0]), math.log(self.ratio[1])))
              new_w = int(round(math.sqrt(area * ratio)))
              new_h = int(round(math.sqrt(area / ratio)))
              if new_w <= w and new_h <= h:
                  left = random.randint(0, w-new_w)
                  top = random.randint(0, h-new_h)
                  return (left, top, left + new_w, top + new_h)
  
          # Fallback to squish
          if w/h < self.ratio[0]: size = (w, int(w/self.ratio[0]))
          elif w/h > self.ratio[1]: size = (int(h*self.ratio[1]), h)
          else: size = (w,h)
          return ((w-size[0])//2, (h-size[1])//2, (w+size[0])//2, (h+size[1])//2)
  
  from torch import FloatTensor, LongTensor
  
  def find_coeffs(orig_pts, targ_pts):
      matrix = []
      # The equations we'll need to solve.
      for p1, p2 in zip(targ_pts, orig_pts):
          matrix.append([p1[0], p1[1], 1, 0, 0, 0, -p2[0]*p1[0], -p2[0]*p1[1]])
          matrix.append([0, 0, 0, p1[0], p1[1], 1, -p2[1]*p1[0], -p2[1]*p1[1]])
  
      A = FloatTensor(matrix)
      B = FloatTensor(orig_pts).view(8,1)
      # The 8 scalars we seek are solution of AX = B
  #     return list(torch.solve(B,A)[0][:,0])
      return list(torch.linalg.solve(A,B)[:,0])
  
  def warp(img, size, src_coords, resample=PIL.Image.BILINEAR):
      w,h = size
      targ_coords = ((0,0), (0,h), (w,h), (w,0))
      c = find_coeffs(src_coords, targ_coords)
      res = img.transform(size, PIL.Image.PERSPECTIVE, list(c), resample=resample)
      return res
  
  def uniform(a,b): return a + (b-a) * random.random()
  
  class PilTiltRandomCrop(PilTransform):
      def __init__(self, size, crop_size=None, magnitude=0., resample=PIL.Image.BILINEAR):
          self.resample,self.size,self.magnitude = resample,process_sz(size),magnitude
          self.crop_size = None if crop_size is None else process_sz(crop_size)
  
      def __call__(self, x):
          csize = default_crop_size(*x.size) if self.crop_size is None else self.crop_size
          left,top = randint(0,x.size[0]-csize[0]),randint(0,x.size[1]-csize[1])
          top_magn = min(self.magnitude, left/csize[0], (x.size[0]-left)/csize[0]-1)
          lr_magn  = min(self.magnitude, top /csize[1], (x.size[1]-top) /csize[1]-1)
          up_t,lr_t = uniform(-top_magn, top_magn),uniform(-lr_magn, lr_magn)
          src_corners = tensor([[-up_t, -lr_t], [up_t, 1+lr_t], [1-up_t, 1-lr_t], [1+up_t, lr_t]])
          src_corners = src_corners * tensor(csize).float() + tensor([left,top]).float()
          src_corners = tuple([(int(o[0].item()), int(o[1].item())) for o in src_corners])
          return warp(x, self.size, src_corners, resample=self.resample)
  
  import numpy as np
  
  def np_to_float(x): return torch.from_numpy(np.array(x, dtype=np.float32, copy=False)).permute(2,0,1).contiguous()/255.
  np_to_float._order = 30