Switch to side-by-side view

--- a
+++ b/utils/myocardial_strain.py
@@ -0,0 +1,654 @@
+import numpy as np
+from scipy.ndimage import rotate
+from scipy.ndimage import gaussian_filter
+from scipy.interpolate import interp1d, interp2d
+from scipy.ndimage.measurements import center_of_mass
+
+class Tensor():
+    
+    def __init__(self, Exx, Exy, Exz, Eyx, Eyy, Eyz, Ezx, Ezy, Ezz):
+        
+        self.E1, self.E2, self.E3 = Exx.copy(), Exy.copy(), Exz.copy()
+        self.E4, self.E5, self.E6 = Eyx.copy(), Eyy.copy(), Eyz.copy()
+        self.E7, self.E8, self.E9 = Ezx.copy(), Ezy.copy(), Ezz.copy()
+
+    def asmat(self):
+        return np.array([[self.E1,self.E2,self.E3],
+                         [self.E4,self.E5,self.E6],
+                         [self.E7,self.E8,self.E9]]).transpose((2,3,4,0,1))
+        
+    def asvoigt(self):
+        return self.E1, self.E2, self.E3, self.E4, self.E5, self.E6, self.E7, self.E8, self.E9 
+        
+    def transpose(self):
+        return Tensor(self.E1, self.E4, self.E7, self.E2, self.E5, self.E8, self.E3, self.E6, self.E9)
+    
+    def identity_add(self):
+        self.E1 += 1; self.E5 += 1; self.E9 += 1 
+    
+    def identity_subtract(self):
+        self.E1 -= 1; self.E5 -= 1; self.E9 -= 1 
+        
+    @staticmethod
+    def dot(X, Y):
+        
+        X1,X2,X3,X4,X5,X6,X7,X8,X9=X.asvoigt()
+        Y1,Y2,Y3,Y4,Y5,Y6,Y7,Y8,Y9=Y.asvoigt()
+        
+        Z1, Z2, Z3 = X1*Y1 + X2*Y4 + X3*Y7, X1*Y2 + X2*Y5 + X3*Y8, X1*Y3 + X2*Y6 + X3*Y9
+        Z4, Z5, Z6 = X4*Y1 + X5*Y4 + X6*Y7, X4*Y2 + X5*Y5 + X6*Y8, X4*Y3 + X5*Y6 + X6*Y9
+        Z7, Z8, Z9 = X7*Y1 + X8*Y4 + X9*Y7, X7*Y2 + X8*Y5 + X9*Y8, X7*Y3 + X8*Y6 + X9*Y9
+        
+        return Tensor(Z1,Z2,Z3,Z4,Z5,Z6,Z7,Z8,Z9)
+    
+
+class MyocardialStrain():
+    
+    def __init__(self, mask, flow):
+                
+        self.mask  = mask
+        self.flow  = flow
+        
+        assert len(mask.shape) == 3
+        assert len(flow.shape) == 4
+        assert mask.shape == flow.shape[:3]
+        assert flow.shape[-1] == 3
+
+    def calculate_strain(self, lv_label=3):
+        
+        cx, cy, cz = center_of_mass(self.mask==lv_label)
+        nx, ny, nz = self.mask.shape
+        
+        self.flow_rot = roll_to_center(self.flow, cx, cy)
+        self.mask_rot = roll_to_center(self.mask, cx, cy)
+
+        ux, uy, uz  = np.array_split(self.flow_rot, 3, -1)
+        Uxx,Uxy,Uxz = np.gradient(np.squeeze(ux))
+        Uyx,Uyy,Uyz = np.gradient(np.squeeze(uy))
+        Uzx,Uzy,Uzz = np.gradient(np.squeeze(uz))
+        
+        F = Tensor(Uxx,Uxy,Uxz,Uyx,Uyy,Uyz,Uzx,Uzy,Uzz)
+
+        F.identity_add()
+        F = F.dot(F.transpose(), F) 
+        F.identity_subtract()
+        
+        self.Err, self.Ecc, self.Erc, self.Ecr = convert_to_polar(mask=self.mask_rot, E=0.5*F.asmat()[:,:,:,:2,:2])
+        
+
+def roll(x, rx, ry):
+    x = np.roll(x, rx, axis=0)
+    return np.roll(x, ry, axis=1)
+
+def roll_to_center(x, cx, cy):
+    nx, ny = x.shape[:2]
+    return roll(x,  int(nx//2-cx), int(ny//2-cy))
+
+def polar_grid(nx=128, ny=128):
+    x, y = np.meshgrid(np.linspace(-nx//2, nx//2, nx), np.linspace(-ny//2, ny//2, ny))
+    phi  = (np.rad2deg(np.arctan2(y, x)) + 180).T
+    r    = np.sqrt(x**2+y**2+1e-8)
+    return phi, r
+
+def convert_to_polar(mask, E):
+
+    phi = polar_grid(*E.shape[:2])[0]
+    Err = np.zeros(mask.shape)
+    Ecc = np.zeros(mask.shape)
+    Erc = np.zeros(mask.shape)
+    Ecr = np.zeros(mask.shape)
+    for k in range(mask.shape[-1]):
+        cos = np.cos(np.deg2rad(phi))
+        sin = np.sin(np.deg2rad(phi))
+
+        Exx, Exy, Eyx, Eyy = E[:,:,k,0,0],E[:,:,k,0,1],E[:,:,k,1,0],E[:,:,k,1,1]
+        Err[:,:,k] +=  cos*( cos*Exx+sin*Exy) + sin*( cos*Eyx+sin*Eyy)
+        Ecc[:,:,k] += -sin*(-sin*Exx+cos*Exy) + cos*(-sin*Eyx+cos*Eyy)
+        Erc[:,:,k] +=  cos*(-sin*Exx+cos*Exy) + sin*(-sin*Eyx+cos*Eyy)
+        Ecr[:,:,k] += -sin*( cos*Exx+sin*Exy) + cos*( cos*Eyx+sin*Eyy)
+
+    return Err, Ecc, Erc, Ecr
+
+    
+def convert_to_aha4d(tensor, mask):
+    Tensor = tensor.copy()
+    Mask   = mask.copy()
+
+    Tensor[Mask!=2,:] = 0
+
+    # rotate to have RV center of mass on the right
+    angle  = _get_lv2rv_angle(Mask)
+    Tensor = rotate(Tensor,  -angle, reshape=False, order=0)
+    Mask   = rotate(Mask, -angle, reshape=False, order=1).clip(0,3).round()
+
+    # roll to center 
+    cx, cy = center_of_mass(Mask>1)[:2]
+    Tensor = np.flipud(np.rot90(_roll_to_center(Tensor, cx, cy)))
+    Mask   = np.flipud(np.rot90(_roll_to_center(Mask, cx, cy)))
+
+    # remove slices that do not contain tissue labels
+    ID     = Mask.sum(axis=(0,1))>0
+    Mask   = Mask[:,:,ID]
+    Tensor = Tensor[:,:,ID]
+    
+    return Tensor, Mask
+    
+class PolarMap():
+    
+    def __init__(self, Err, Ecc, mask):
+        """ Plot Err and Ecc in a PolarMap using a segmentation of the heart as reference. 
+            Assuming mask==2 yields the myocardium labels. 
+        
+        """
+        
+        self.Err  = Err
+        self.Ecc  = Ecc
+        self.mask = mask
+        
+    def project_to_aha_polar_map(self):
+       
+        Err  = self.Err.copy()
+        Ecc  = self.Ecc.copy()
+        mask = self.mask.copy()
+
+        Err[mask!=2] = 0
+        Ecc[mask!=2] = 0
+
+        # rotate to have RV center of mass on the right
+        angle = _get_lv2rv_angle(mask)
+        Ecc   = rotate(Ecc,  -angle, reshape=False, order=0)
+        Err   = rotate(Err,  -angle, reshape=False, order=0)
+        mask  = rotate(mask, -angle, reshape=False, order=1).clip(0,3).round()
+        
+        # roll to center 
+        cx, cy = center_of_mass(mask>1)[:2]
+        Ecc  = np.flipud(np.rot90(_roll_to_center(Ecc, cx, cy)))
+        Err  = np.flipud(np.rot90(_roll_to_center(Err, cx, cy)))
+        mask = np.flipud(np.rot90(_roll_to_center(mask, cx, cy)))
+        
+        # remove slices that do not contain tissue labels
+        ID   = mask.sum(axis=(0,1))>0
+        mask = mask[:,:,ID]
+        Err  = Err[:,:,ID]
+        Ecc  = Ecc[:,:,ID]
+
+
+        
+
+        Err = Err.transpose((2,0,1))
+        Ecc = Ecc.transpose((2,0,1))
+        print('... radial strain')
+        V_err = self._project_to_aha_polar_map(Err)
+        print('... circumferential strain')
+        V_ecc = self._project_to_aha_polar_map(Ecc)
+
+        results = {'V_err':V_err, 'V_ecc':V_ecc, 'mask':mask}
+
+        return results
+        
+    def _project_to_aha_polar_map(self, E, nphi=360, nrad=100, dphi=1):
+        
+        nz = E.shape[0]
+        angles = np.arange(0, nphi, dphi)
+        V      = np.zeros((nz, angles.shape[0], nrad))
+
+        for rj in range(nz):
+
+            Err_q  = _inpter2(E[rj])
+
+            PHI, R = _polar_grid(*Err_q.shape)
+            PHI = PHI.ravel()
+            R   = R.ravel()
+
+            for k, pmin in enumerate(angles):
+                pmax = pmin + dphi/2.0
+
+                # Get values for angle segment
+                PHI_SEGMENT = (PHI>=pmin)&(PHI<=pmax)
+                Rk   = R[PHI_SEGMENT]
+                PHIk = PHI[PHI_SEGMENT]
+                Vk   = Err_q.ravel()[PHI_SEGMENT]
+                
+                
+                Rk = Rk[np.abs(Vk)!=0]
+                Vk = Vk[np.abs(Vk)!=0]
+                
+                if len(Vk) == 0:
+                    continue # this might not be the best
+                Rk = _rescale_linear(Rk, rj, rj + 1)
+
+                r = np.arange(rj, rj+1, 1.0/nrad)
+                f = interp1d(Rk, Vk)
+                v = f(r)
+
+                V[rj,k] += v
+
+        return V        
+        
+        
+    def construct_polar_map(self, tensor, start=30, stop=70, sigma=12):
+
+        E  = tensor.copy()
+        mu = E[:,:,start:stop].mean()
+
+        nz = E.shape[0]
+        E  = np.concatenate(np.array_split(E[:,:,start:stop], nz), axis=-1)[0]
+
+        old = E.shape[1]/nz*1.
+        for j in range(nz-1):
+            xi = int(old//2+j*old)
+            xj = int(old+old//2+j*old)
+            E[:,xi:xj] = gaussian_filter(E[:,xi:xj],sigma=sigma, mode='wrap')
+            E[:,xi:xj] = gaussian_filter(E[:,xi:xj],sigma=sigma, mode='wrap')
+
+        E = np.stack(np.array_split(E,nz,axis=1))
+
+        E = gaussian_filter(E, sigma=sigma, mode='wrap')
+        E = gaussian_filter(E, sigma=sigma, mode='wrap')
+
+        E = [E[0][None]] + [E[1:3]] + np.array_split(E[3:], 2, axis=0)
+
+        E = [np.mean(E[i], axis=0) for i in range(4)]
+        E = np.concatenate(E, axis=1)
+
+        old = E.shape[1]/4
+        for j in range(3):
+            xi = int(old//2+j*old)
+            xj = int(old+old//2+j*old)
+            E[:,xi:xj] = gaussian_filter(E[:,xi:xj], sigma=sigma, mode='wrap')
+            E[:,xi:xj] = gaussian_filter(E[:,xi:xj], sigma=sigma, mode='wrap')
+
+        E = gaussian_filter(E, sigma=sigma, mode='wrap')
+        E = gaussian_filter(E, sigma=sigma, mode='wrap')
+
+        mu = [mu] + self._get_17segments(E)
+
+        return E, mu 
+    
+    def _get_17segments(self, data):
+        c1,c2,c3,c4 = np.array_split(data,4,axis=-1)
+        c2 = np.roll(c2,-45,0)
+        #c2 = np.roll(c2,-90,0)
+
+        c4 = [np.mean(ci) for ci in np.array_split(c4,6,axis=0)]
+        c4 = list(np.roll(np.array(c4),-1))
+        c3 = [np.mean(ci) for ci in np.array_split(c3,6,axis=0)]
+        c3 = list(np.roll(np.array(c3),-1))
+        c2 = [np.mean(ci) for ci in np.array_split(c2,4,axis=0)]
+        #c2 = list(np.roll(np.array(c2),-1))
+        c1 = [np.mean(c1)]
+
+        c = c4 + c3 + c2 + c1
+
+        return c
+
+    def _get_17segments_RC(self, data1,data2):
+        
+        def _rc(a,b):
+            #return np.mean(np.abs((b-a)/b)*100)
+            return np.mean(((b-a)/b)*100)
+        
+        c1_1,c2_1,c3_1,c4_1 = np.array_split(data1,4,axis=-1)
+        c1_2,c2_2,c3_2,c4_2 = np.array_split(data2,4,axis=-1)
+
+        c4 = [_rc(ci1,ci2) for ci1,ci2 in zip(np.array_split(c4_1,6,axis=0),
+                                              np.array_split(c4_2,6,axis=0))]
+        c4 = list(np.roll(np.array(c4),-1))
+        
+        c3 = [_rc(ci1,ci2) for ci1,ci2 in zip(np.array_split(c3_1,6,axis=0),
+                                              np.array_split(c3_2,6,axis=0))]
+        c3 = list(np.roll(np.array(c3),-1))
+        
+        c2 = [_rc(ci1,ci2) for ci1,ci2 in zip(np.array_split(c2_1,4,axis=0),
+                                              np.array_split(c2_2,4,axis=0))]
+        c2 = list(np.roll(np.array(c2),-1))
+        c1 = [_rc(c1_1,c1_2)]
+
+        c = c4 + c3 + c2 + c1
+
+        return c
+    
+def _roll(x, rx, ry):
+    x = np.roll(x, rx, axis=0)
+    return np.roll(x, ry, axis=1)
+
+def _roll_to_center(x, cx, cy):
+    nx, ny = x.shape[:2]
+    return _roll(x,  int(nx//2-cx), int(ny//2-cy))
+
+def _py_ang(v1, v2):
+    """ Returns the angle in degrees between vectors 'v1' and 'v2'. """
+    cosang = np.dot(v1, v2)
+    sinang = np.linalg.norm(np.cross(v1, v2))
+    return np.rad2deg(np.arctan2(sinang, cosang))
+
+def _polar_grid(nx=128, ny=128):
+    x, y = np.meshgrid(np.linspace(-nx//2, nx//2, nx), np.linspace(-ny//2, ny//2, ny))
+    phi  = (np.rad2deg(np.arctan2(y, x)) + 180).T
+    r    = np.sqrt(x**2+y**2+1e-8)
+    return phi, r
+
+def _rescale_linear(array, new_min, new_max):
+    minimum, maximum = np.min(array), np.max(array)
+    m = (new_max-new_min) / (maximum-minimum)
+    b = new_min - m* minimum
+    return m*array + b
+
+def _inpter2(Eij, k=10):
+    nx, ny = Eij.shape
+    
+    x  = np.linspace(0,nx-1,nx)
+    y  = np.linspace(0,ny-1,ny)
+    xq = np.linspace(0,nx-1,nx*k)
+    yq = np.linspace(0,ny-1,ny*k)
+    
+    f = interp2d(x,y,Eij,kind='linear')
+    
+    return f(xq,yq)    
+    
+def _get_lv2rv_angle(mask):
+    cx_lv, cy_lv = center_of_mass(mask>1)[:2]
+    cx_rv, cy_rv = center_of_mass(mask==1)[:2]
+    phi_angle    = _py_ang([cx_rv-cx_lv, cy_rv-cy_lv], [0, 1])
+    return phi_angle    
+    
+    
+    
+### FUNCTIONS TO PLOT THE POLAR MAP 
+
+import numpy as np
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+from matplotlib.lines import Line2D
+from copy import deepcopy
+
+def _write(ax, mu, j, theta_i, i, width=2):
+    xi, yi = polar2cart(0,  theta_i)
+    xf, yf = polar2cart(35, theta_i)
+
+    l = Line2D([40-xi,40-xf], [40-yi,40-yf], color='black', linewidth=width)
+    ax.add_line(l)
+    xi, yi = polar2cart(30, theta_i + 2*np.pi/12)
+    ax.text(40-xi-.3, 40-yi, '%d' %(mu[j][i]), weight='bold', fontsize=14)
+    
+def write(ax, mu, j, width=2):
+    if j > 1:
+        for i in range(6):
+            theta_i = 2*np.pi - i*60*np.pi/180 + 2*60*np.pi/180
+            _write(ax, mu, j, theta_i, i)
+    if j == 1:
+        for i in range(4):
+            theta_i = i*90*np.pi/180 - 45*np.pi/180
+            _write(ax, mu, j, theta_i, i)
+    if j ==0:
+        ax.text(40-.3, 40, '%d' %(mu[j][0]), weight='bold', fontsize=14)
+
+def plot_bullseye(data,mu,vmin=None,vmax=None, savepath=None,cmap='RdBu_r', label='GPRS (%)', 
+                  std=None,cbar=False,color='white', fs=20, xshift=0, yshift=0, ptype='mesh',frac=False):
+    
+    rho     = np.arange(0,4,4.0/data.shape[1]);
+    Theta   = np.deg2rad(range(data.shape[0]))
+    [th, r] = np.meshgrid(Theta, rho);
+
+    fig, ax = plt.subplots(figsize=(6,6))
+    #fig.subplots_adjust(left=0,right=1,bottom=0,top=1)
+    #ax.axis('tight') creates errors 
+    #ax.axis('off')
+    if ptype == 'mesh':
+        im = ax.pcolormesh(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
+                           vmin=vmin,vmax=vmax,cmap=cmap,shading='gouraud')
+    else:
+        im = ax.contourf(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
+                           vmin=vmin,vmax=vmax,cmap=cmap,shading='gouraud')
+    if cbar:
+        cbar = plt.colorbar(im, cax=fig.add_axes([0.15, -0.03, 0.7, 0.05]), orientation='horizontal')
+
+        new_ticks = []
+        new_ticks_labels = []
+        for i,tick in enumerate(cbar.ax.get_xticks()):
+            if i % 2 == 0:
+                new_ticks.append(np.round(tick))
+                new_ticks_labels.append(str(int(np.round(tick))))
+
+        cbar.set_ticks(new_ticks);
+        cbar.set_ticklabels(new_ticks_labels);
+
+        # override if vmin is provided, assume vmax is provided too for now
+        if vmin is not None:
+            cbar.set_ticks([vmin, (vmax+vmin)/2.0, vmax]);
+            cbar.set_ticklabels(['%d'%(i) for i in [vmin, (vmax+vmin)/2.0, vmax]]);
+        cbar.ax.tick_params(labelsize=18)
+        cbar.set_label(label, fontsize=26, weight='bold')
+
+    ax.axis('off')
+    if std is not None:
+        draw_circle_group(ax,100*np.array(mu),100*np.array(std))
+    if frac:
+        draw_circle_frac(ax,np.array(mu), color=color)
+    else:
+        draw_circle(ax,100*np.array(mu), color=color, fs=fs, xshift=xshift, yshift=yshift)
+    if savepath is not None:
+        if not cbar:
+            plt.tight_layout()
+        plt.savefig(savepath, dpi=600)
+    plt.show()
+    
+def plot_bullseye_ratio(data,mu,vmin=None,vmax=None, savepath=None,cmap='RdBu_r', label='GPRS (%)', 
+                  std=None,cbar=False,color='white',ptype='mesh',frac=False):
+    
+    rho     = np.arange(0,4,4.0/data.shape[1]);
+    Theta   = np.deg2rad(range(data.shape[0]))
+    [th, r] = np.meshgrid(Theta, rho);
+
+    fig, ax = plt.subplots(figsize=(6,6))
+
+    if ptype == 'mesh':
+        im = ax.pcolormesh(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
+                           vmin=vmin,vmax=vmax,cmap=cmap,shading='gouraud')
+    else:
+        im = ax.contourf(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
+                           vmin=vmin,vmax=vmax,cmap=cmap,shading='gouraud')
+    cbar = plt.colorbar(im, cax=fig.add_axes([0.15, -0.03, 0.7, 0.05]), orientation='horizontal')
+
+    draw_circle_error(ax)
+    ax.axis('off')
+    if savepath is not None:
+        if not cbar:
+            plt.tight_layout()
+        plt.savefig(savepath, dpi=600)
+    plt.show()
+    
+def plot_bullseye_error(data,mu,vmin=None,vmax=None, savepath=None,cmap='RdBu_r', label='GPRS (%)',n=5):
+    
+    rho     = np.arange(0,4,4.0/data.shape[1]);
+    Theta   = np.deg2rad(range(data.shape[0]))
+    [th, r] = np.meshgrid(Theta, rho);
+
+    fig, ax = plt.subplots(figsize=(6,6))
+
+    levels = np.linspace(vmin, vmax, n+1)
+    im = ax.contourf(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
+                           vmin=vmin,vmax=vmax,cmap=cmap,levels=levels)
+
+    cbar = plt.colorbar(im, cax=fig.add_axes([0.15, -0.03, 0.7, 0.05]), orientation='horizontal')
+
+    #ticks = -np.array(range(0,120,20))
+
+    #cbar.set_ticks(ticks);
+    #cbar.set_ticklabels(['%d'%(i) for i in ticks]);
+
+     
+
+    ax.axis('off')
+    draw_circle_error(ax)
+    if savepath is not None:
+        if not cbar:
+            plt.tight_layout()
+        plt.savefig(savepath, dpi=500)
+    plt.show()
+
+def draw_circle_error(ax,width=4):
+
+    circle1 = plt.Circle((0,0), 1, color='black', fill=False, linewidth=width)
+    circle2 = plt.Circle((0,0), 2, color='black', fill=False, linewidth=width)
+    circle3 = plt.Circle((0,0), 3, color='black', fill=False, linewidth=width)
+    circle4 = plt.Circle((0,0), 4, color='black', fill=False, linewidth=width)
+
+    ax.add_artist(circle1)
+    ax.add_artist(circle2)
+    ax.add_artist(circle3)
+    ax.add_artist(circle4)
+    
+    j = 0
+    for i in range(6):
+        theta_i = i*60*np.pi/180 + 60*np.pi/180
+        xi, yi = polar2cart(2, theta_i)
+        xf, yf = polar2cart(4, theta_i)
+        
+        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
+        ax.add_line(l)
+
+    j += 6
+    for i in range(4):
+        theta_i = i*90*(np.pi/180) - 45
+        xi, yi  = polar2cart(1, theta_i)
+        xf, yf  = polar2cart(2, theta_i)
+        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
+        ax.add_line(l)
+
+def draw_circle_frac(ax, mu, width=4, fs=20, color='white'):
+    
+    
+    
+    circle1 = plt.Circle((0,0), 1, color='black', fill=False, linewidth=width)
+    circle2 = plt.Circle((0,0), 2, color='black', fill=False, linewidth=width)
+    circle3 = plt.Circle((0,0), 3, color='black', fill=False, linewidth=width)
+    circle4 = plt.Circle((0,0), 4, color='black', fill=False, linewidth=width)
+
+    ax.add_artist(circle1)
+    ax.add_artist(circle2)
+    ax.add_artist(circle3)
+    ax.add_artist(circle4)
+    
+    j = 0
+    for i in range(6):
+        theta_i = i*60*np.pi/180 + 60*np.pi/180
+        xi, yi = polar2cart(2, theta_i)
+        xf, yf = polar2cart(4, theta_i)
+        
+        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
+        ax.add_line(l)
+        
+        xi, yi = polar2cart(3.5, theta_i + 2*np.pi/12)
+        ax.text(xi-.3, yi, '%.2f' %(mu[j]), weight='bold', fontsize=fs, color=color);
+        xi, yi = polar2cart(2.5, theta_i + 2*np.pi/12)
+        ax.text(xi-.3, yi, '%.2f' %(mu[j+6]), weight='bold', fontsize=fs, color=color); j += 1
+        
+    j += 6
+    LABELS = ['ANT', 'SEPT', 'INF', 'LAT']
+    for i in range(4):
+        theta_i = i*90*np.pi/180 - 45 
+        xi, yi = polar2cart(1, theta_i)
+        xf, yf = polar2cart(2, theta_i)
+        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
+        ax.add_line(l)
+        
+        xi, yi = polar2cart(1.5, theta_i + 2*np.pi/8)
+
+        ax.text(xi-.3, yi, '%.2f' %(mu[j]), weight='bold', fontsize=fs, color=color); j += 1;
+        xi, yi = polar2cart(5, theta_i + 2*np.pi/8)
+
+    ax.text(0-.3, 0-.3, '%.2f' %(mu[j]), weight='bold', fontsize=fs, color=color)
+    
+def draw_circle(ax, mu, width=4, fs=15, xshift=0, yshift=0, color='white'):
+    
+    
+    
+    circle1 = plt.Circle((0,0), 1, color='black', fill=False, linewidth=width)
+    circle2 = plt.Circle((0,0), 2, color='black', fill=False, linewidth=width)
+    circle3 = plt.Circle((0,0), 3, color='black', fill=False, linewidth=width)
+    circle4 = plt.Circle((0,0), 4, color='black', fill=False, linewidth=width)
+
+    ax.add_artist(circle1)
+    ax.add_artist(circle2)
+    ax.add_artist(circle3)
+    ax.add_artist(circle4)
+    
+    j = 0
+    for i in range(6):
+        theta_i = i*60*np.pi/180 + 60*np.pi/180
+        xi, yi = polar2cart(2, theta_i)
+        xf, yf = polar2cart(4, theta_i)
+        
+        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
+        ax.add_line(l)
+        
+        xi, yi = polar2cart(3.5, theta_i + 2*np.pi/12)
+        ax.text(xi-.4-xshift, yi-yshift, '%d' %(mu[j]), weight='bold', fontsize=fs, color=color);
+        xi, yi = polar2cart(2.5, theta_i + 2*np.pi/12)
+        ax.text(xi-.4-xshift, yi-yshift, '%d' %(mu[j+6]), weight='bold', fontsize=fs, color=color); j += 1
+        
+    j += 6
+    LABELS = ['ANT', 'SEPT', 'INF', 'LAT']
+    for i in range(4):
+        theta_i = i*90*np.pi/180  + 45*np.pi/180
+        xi, yi = polar2cart(1, theta_i)
+        xf, yf = polar2cart(2, theta_i)
+        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
+        ax.add_line(l)
+        
+        xi, yi = polar2cart(1.5, theta_i + 2*np.pi/8)
+
+        ax.text(xi-.4-xshift, yi-yshift, '%d' %(mu[j]), weight='bold', fontsize=fs, color=color); j += 1;
+        xi, yi = polar2cart(5, theta_i + 2*np.pi/8)
+
+    ax.text(-.4-xshift, 0-yshift, '%d' %(mu[j]), weight='bold', fontsize=fs, color=color)
+  
+
+def draw_circle_group(ax, mu, std, width=4, fs=14, color='white'):
+    
+        
+    circle1 = plt.Circle((0,0), 1, color='black', fill=False, linewidth=width)
+    circle2 = plt.Circle((0,0), 2, color='black', fill=False, linewidth=width)
+    circle3 = plt.Circle((0,0), 3, color='black', fill=False, linewidth=width)
+    circle4 = plt.Circle((0,0), 4, color='black', fill=False, linewidth=width)
+
+    ax.add_artist(circle1)
+    ax.add_artist(circle2)
+    ax.add_artist(circle3)
+    ax.add_artist(circle4)
+    
+    j = 0
+    for i in range(6):
+        theta_i = i*60*np.pi/180 + 60*np.pi/180
+        xi, yi = polar2cart(2, theta_i)
+        xf, yf = polar2cart(4, theta_i)
+        
+        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
+        ax.add_line(l)
+        
+        xi, yi = polar2cart(3.5, theta_i + 2*np.pi/12)
+        ax.text(xi-.6, yi, '%d(%d)' %(mu[j],std[j]), weight='bold', fontsize=fs, color=color);
+        xi, yi = polar2cart(2.5, theta_i + 2*np.pi/12)
+        ax.text(xi-.6, yi, '%d(%d)' %(mu[j+6],std[j+6]), weight='bold', fontsize=fs, color=color); j += 1
+        
+    j += 6
+    LABELS = ['ANT', 'SEPT', 'INF', 'LAT']
+    for i in range(4):
+        theta_i = i*90*np.pi/180 
+        xi, yi = polar2cart(1, theta_i)
+        xf, yf = polar2cart(2, theta_i)
+        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
+        ax.add_line(l)
+        
+        xi, yi = polar2cart(1.5, theta_i + 2*np.pi/8)
+
+        ax.text(xi-.6, yi-0.1, '%d(%d)' %(mu[j],std[j]), weight='bold', fontsize=fs, color=color); j += 1;
+        xi, yi = polar2cart(5, theta_i + 2*np.pi/8)
+
+    ax.text(0-.3, 0-.2, '%d(%d)' %(mu[j],std[j]), weight='bold', fontsize=fs, color=color)
+    
+def polar2cart(r, theta):
+    x = r * np.cos(theta)
+    y = r * np.sin(theta)
+    return x, y
\ No newline at end of file