Switch to unified view

a b/utils/myocardial_strain.py
1
import numpy as np
2
from scipy.ndimage import rotate
3
from scipy.ndimage import gaussian_filter
4
from scipy.interpolate import interp1d, interp2d
5
from scipy.ndimage.measurements import center_of_mass
6
7
class Tensor():
8
    
9
    def __init__(self, Exx, Exy, Exz, Eyx, Eyy, Eyz, Ezx, Ezy, Ezz):
10
        
11
        self.E1, self.E2, self.E3 = Exx.copy(), Exy.copy(), Exz.copy()
12
        self.E4, self.E5, self.E6 = Eyx.copy(), Eyy.copy(), Eyz.copy()
13
        self.E7, self.E8, self.E9 = Ezx.copy(), Ezy.copy(), Ezz.copy()
14
15
    def asmat(self):
16
        return np.array([[self.E1,self.E2,self.E3],
17
                         [self.E4,self.E5,self.E6],
18
                         [self.E7,self.E8,self.E9]]).transpose((2,3,4,0,1))
19
        
20
    def asvoigt(self):
21
        return self.E1, self.E2, self.E3, self.E4, self.E5, self.E6, self.E7, self.E8, self.E9 
22
        
23
    def transpose(self):
24
        return Tensor(self.E1, self.E4, self.E7, self.E2, self.E5, self.E8, self.E3, self.E6, self.E9)
25
    
26
    def identity_add(self):
27
        self.E1 += 1; self.E5 += 1; self.E9 += 1 
28
    
29
    def identity_subtract(self):
30
        self.E1 -= 1; self.E5 -= 1; self.E9 -= 1 
31
        
32
    @staticmethod
33
    def dot(X, Y):
34
        
35
        X1,X2,X3,X4,X5,X6,X7,X8,X9=X.asvoigt()
36
        Y1,Y2,Y3,Y4,Y5,Y6,Y7,Y8,Y9=Y.asvoigt()
37
        
38
        Z1, Z2, Z3 = X1*Y1 + X2*Y4 + X3*Y7, X1*Y2 + X2*Y5 + X3*Y8, X1*Y3 + X2*Y6 + X3*Y9
39
        Z4, Z5, Z6 = X4*Y1 + X5*Y4 + X6*Y7, X4*Y2 + X5*Y5 + X6*Y8, X4*Y3 + X5*Y6 + X6*Y9
40
        Z7, Z8, Z9 = X7*Y1 + X8*Y4 + X9*Y7, X7*Y2 + X8*Y5 + X9*Y8, X7*Y3 + X8*Y6 + X9*Y9
41
        
42
        return Tensor(Z1,Z2,Z3,Z4,Z5,Z6,Z7,Z8,Z9)
43
    
44
45
class MyocardialStrain():
46
    
47
    def __init__(self, mask, flow):
48
                
49
        self.mask  = mask
50
        self.flow  = flow
51
        
52
        assert len(mask.shape) == 3
53
        assert len(flow.shape) == 4
54
        assert mask.shape == flow.shape[:3]
55
        assert flow.shape[-1] == 3
56
57
    def calculate_strain(self, lv_label=3):
58
        
59
        cx, cy, cz = center_of_mass(self.mask==lv_label)
60
        nx, ny, nz = self.mask.shape
61
        
62
        self.flow_rot = roll_to_center(self.flow, cx, cy)
63
        self.mask_rot = roll_to_center(self.mask, cx, cy)
64
65
        ux, uy, uz  = np.array_split(self.flow_rot, 3, -1)
66
        Uxx,Uxy,Uxz = np.gradient(np.squeeze(ux))
67
        Uyx,Uyy,Uyz = np.gradient(np.squeeze(uy))
68
        Uzx,Uzy,Uzz = np.gradient(np.squeeze(uz))
69
        
70
        F = Tensor(Uxx,Uxy,Uxz,Uyx,Uyy,Uyz,Uzx,Uzy,Uzz)
71
72
        F.identity_add()
73
        F = F.dot(F.transpose(), F) 
74
        F.identity_subtract()
75
        
76
        self.Err, self.Ecc, self.Erc, self.Ecr = convert_to_polar(mask=self.mask_rot, E=0.5*F.asmat()[:,:,:,:2,:2])
77
        
78
79
def roll(x, rx, ry):
80
    x = np.roll(x, rx, axis=0)
81
    return np.roll(x, ry, axis=1)
82
83
def roll_to_center(x, cx, cy):
84
    nx, ny = x.shape[:2]
85
    return roll(x,  int(nx//2-cx), int(ny//2-cy))
86
87
def polar_grid(nx=128, ny=128):
88
    x, y = np.meshgrid(np.linspace(-nx//2, nx//2, nx), np.linspace(-ny//2, ny//2, ny))
89
    phi  = (np.rad2deg(np.arctan2(y, x)) + 180).T
90
    r    = np.sqrt(x**2+y**2+1e-8)
91
    return phi, r
92
93
def convert_to_polar(mask, E):
94
95
    phi = polar_grid(*E.shape[:2])[0]
96
    Err = np.zeros(mask.shape)
97
    Ecc = np.zeros(mask.shape)
98
    Erc = np.zeros(mask.shape)
99
    Ecr = np.zeros(mask.shape)
100
    for k in range(mask.shape[-1]):
101
        cos = np.cos(np.deg2rad(phi))
102
        sin = np.sin(np.deg2rad(phi))
103
104
        Exx, Exy, Eyx, Eyy = E[:,:,k,0,0],E[:,:,k,0,1],E[:,:,k,1,0],E[:,:,k,1,1]
105
        Err[:,:,k] +=  cos*( cos*Exx+sin*Exy) + sin*( cos*Eyx+sin*Eyy)
106
        Ecc[:,:,k] += -sin*(-sin*Exx+cos*Exy) + cos*(-sin*Eyx+cos*Eyy)
107
        Erc[:,:,k] +=  cos*(-sin*Exx+cos*Exy) + sin*(-sin*Eyx+cos*Eyy)
108
        Ecr[:,:,k] += -sin*( cos*Exx+sin*Exy) + cos*( cos*Eyx+sin*Eyy)
109
110
    return Err, Ecc, Erc, Ecr
111
112
    
113
def convert_to_aha4d(tensor, mask):
114
    Tensor = tensor.copy()
115
    Mask   = mask.copy()
116
117
    Tensor[Mask!=2,:] = 0
118
119
    # rotate to have RV center of mass on the right
120
    angle  = _get_lv2rv_angle(Mask)
121
    Tensor = rotate(Tensor,  -angle, reshape=False, order=0)
122
    Mask   = rotate(Mask, -angle, reshape=False, order=1).clip(0,3).round()
123
124
    # roll to center 
125
    cx, cy = center_of_mass(Mask>1)[:2]
126
    Tensor = np.flipud(np.rot90(_roll_to_center(Tensor, cx, cy)))
127
    Mask   = np.flipud(np.rot90(_roll_to_center(Mask, cx, cy)))
128
129
    # remove slices that do not contain tissue labels
130
    ID     = Mask.sum(axis=(0,1))>0
131
    Mask   = Mask[:,:,ID]
132
    Tensor = Tensor[:,:,ID]
133
    
134
    return Tensor, Mask
135
    
136
class PolarMap():
137
    
138
    def __init__(self, Err, Ecc, mask):
139
        """ Plot Err and Ecc in a PolarMap using a segmentation of the heart as reference. 
140
            Assuming mask==2 yields the myocardium labels. 
141
        
142
        """
143
        
144
        self.Err  = Err
145
        self.Ecc  = Ecc
146
        self.mask = mask
147
        
148
    def project_to_aha_polar_map(self):
149
       
150
        Err  = self.Err.copy()
151
        Ecc  = self.Ecc.copy()
152
        mask = self.mask.copy()
153
154
        Err[mask!=2] = 0
155
        Ecc[mask!=2] = 0
156
157
        # rotate to have RV center of mass on the right
158
        angle = _get_lv2rv_angle(mask)
159
        Ecc   = rotate(Ecc,  -angle, reshape=False, order=0)
160
        Err   = rotate(Err,  -angle, reshape=False, order=0)
161
        mask  = rotate(mask, -angle, reshape=False, order=1).clip(0,3).round()
162
        
163
        # roll to center 
164
        cx, cy = center_of_mass(mask>1)[:2]
165
        Ecc  = np.flipud(np.rot90(_roll_to_center(Ecc, cx, cy)))
166
        Err  = np.flipud(np.rot90(_roll_to_center(Err, cx, cy)))
167
        mask = np.flipud(np.rot90(_roll_to_center(mask, cx, cy)))
168
        
169
        # remove slices that do not contain tissue labels
170
        ID   = mask.sum(axis=(0,1))>0
171
        mask = mask[:,:,ID]
172
        Err  = Err[:,:,ID]
173
        Ecc  = Ecc[:,:,ID]
174
175
176
        
177
178
        Err = Err.transpose((2,0,1))
179
        Ecc = Ecc.transpose((2,0,1))
180
        print('... radial strain')
181
        V_err = self._project_to_aha_polar_map(Err)
182
        print('... circumferential strain')
183
        V_ecc = self._project_to_aha_polar_map(Ecc)
184
185
        results = {'V_err':V_err, 'V_ecc':V_ecc, 'mask':mask}
186
187
        return results
188
        
189
    def _project_to_aha_polar_map(self, E, nphi=360, nrad=100, dphi=1):
190
        
191
        nz = E.shape[0]
192
        angles = np.arange(0, nphi, dphi)
193
        V      = np.zeros((nz, angles.shape[0], nrad))
194
195
        for rj in range(nz):
196
197
            Err_q  = _inpter2(E[rj])
198
199
            PHI, R = _polar_grid(*Err_q.shape)
200
            PHI = PHI.ravel()
201
            R   = R.ravel()
202
203
            for k, pmin in enumerate(angles):
204
                pmax = pmin + dphi/2.0
205
206
                # Get values for angle segment
207
                PHI_SEGMENT = (PHI>=pmin)&(PHI<=pmax)
208
                Rk   = R[PHI_SEGMENT]
209
                PHIk = PHI[PHI_SEGMENT]
210
                Vk   = Err_q.ravel()[PHI_SEGMENT]
211
                
212
                
213
                Rk = Rk[np.abs(Vk)!=0]
214
                Vk = Vk[np.abs(Vk)!=0]
215
                
216
                if len(Vk) == 0:
217
                    continue # this might not be the best
218
                Rk = _rescale_linear(Rk, rj, rj + 1)
219
220
                r = np.arange(rj, rj+1, 1.0/nrad)
221
                f = interp1d(Rk, Vk)
222
                v = f(r)
223
224
                V[rj,k] += v
225
226
        return V        
227
        
228
        
229
    def construct_polar_map(self, tensor, start=30, stop=70, sigma=12):
230
231
        E  = tensor.copy()
232
        mu = E[:,:,start:stop].mean()
233
234
        nz = E.shape[0]
235
        E  = np.concatenate(np.array_split(E[:,:,start:stop], nz), axis=-1)[0]
236
237
        old = E.shape[1]/nz*1.
238
        for j in range(nz-1):
239
            xi = int(old//2+j*old)
240
            xj = int(old+old//2+j*old)
241
            E[:,xi:xj] = gaussian_filter(E[:,xi:xj],sigma=sigma, mode='wrap')
242
            E[:,xi:xj] = gaussian_filter(E[:,xi:xj],sigma=sigma, mode='wrap')
243
244
        E = np.stack(np.array_split(E,nz,axis=1))
245
246
        E = gaussian_filter(E, sigma=sigma, mode='wrap')
247
        E = gaussian_filter(E, sigma=sigma, mode='wrap')
248
249
        E = [E[0][None]] + [E[1:3]] + np.array_split(E[3:], 2, axis=0)
250
251
        E = [np.mean(E[i], axis=0) for i in range(4)]
252
        E = np.concatenate(E, axis=1)
253
254
        old = E.shape[1]/4
255
        for j in range(3):
256
            xi = int(old//2+j*old)
257
            xj = int(old+old//2+j*old)
258
            E[:,xi:xj] = gaussian_filter(E[:,xi:xj], sigma=sigma, mode='wrap')
259
            E[:,xi:xj] = gaussian_filter(E[:,xi:xj], sigma=sigma, mode='wrap')
260
261
        E = gaussian_filter(E, sigma=sigma, mode='wrap')
262
        E = gaussian_filter(E, sigma=sigma, mode='wrap')
263
264
        mu = [mu] + self._get_17segments(E)
265
266
        return E, mu 
267
    
268
    def _get_17segments(self, data):
269
        c1,c2,c3,c4 = np.array_split(data,4,axis=-1)
270
        c2 = np.roll(c2,-45,0)
271
        #c2 = np.roll(c2,-90,0)
272
273
        c4 = [np.mean(ci) for ci in np.array_split(c4,6,axis=0)]
274
        c4 = list(np.roll(np.array(c4),-1))
275
        c3 = [np.mean(ci) for ci in np.array_split(c3,6,axis=0)]
276
        c3 = list(np.roll(np.array(c3),-1))
277
        c2 = [np.mean(ci) for ci in np.array_split(c2,4,axis=0)]
278
        #c2 = list(np.roll(np.array(c2),-1))
279
        c1 = [np.mean(c1)]
280
281
        c = c4 + c3 + c2 + c1
282
283
        return c
284
285
    def _get_17segments_RC(self, data1,data2):
286
        
287
        def _rc(a,b):
288
            #return np.mean(np.abs((b-a)/b)*100)
289
            return np.mean(((b-a)/b)*100)
290
        
291
        c1_1,c2_1,c3_1,c4_1 = np.array_split(data1,4,axis=-1)
292
        c1_2,c2_2,c3_2,c4_2 = np.array_split(data2,4,axis=-1)
293
294
        c4 = [_rc(ci1,ci2) for ci1,ci2 in zip(np.array_split(c4_1,6,axis=0),
295
                                              np.array_split(c4_2,6,axis=0))]
296
        c4 = list(np.roll(np.array(c4),-1))
297
        
298
        c3 = [_rc(ci1,ci2) for ci1,ci2 in zip(np.array_split(c3_1,6,axis=0),
299
                                              np.array_split(c3_2,6,axis=0))]
300
        c3 = list(np.roll(np.array(c3),-1))
301
        
302
        c2 = [_rc(ci1,ci2) for ci1,ci2 in zip(np.array_split(c2_1,4,axis=0),
303
                                              np.array_split(c2_2,4,axis=0))]
304
        c2 = list(np.roll(np.array(c2),-1))
305
        c1 = [_rc(c1_1,c1_2)]
306
307
        c = c4 + c3 + c2 + c1
308
309
        return c
310
    
311
def _roll(x, rx, ry):
312
    x = np.roll(x, rx, axis=0)
313
    return np.roll(x, ry, axis=1)
314
315
def _roll_to_center(x, cx, cy):
316
    nx, ny = x.shape[:2]
317
    return _roll(x,  int(nx//2-cx), int(ny//2-cy))
318
319
def _py_ang(v1, v2):
320
    """ Returns the angle in degrees between vectors 'v1' and 'v2'. """
321
    cosang = np.dot(v1, v2)
322
    sinang = np.linalg.norm(np.cross(v1, v2))
323
    return np.rad2deg(np.arctan2(sinang, cosang))
324
325
def _polar_grid(nx=128, ny=128):
326
    x, y = np.meshgrid(np.linspace(-nx//2, nx//2, nx), np.linspace(-ny//2, ny//2, ny))
327
    phi  = (np.rad2deg(np.arctan2(y, x)) + 180).T
328
    r    = np.sqrt(x**2+y**2+1e-8)
329
    return phi, r
330
331
def _rescale_linear(array, new_min, new_max):
332
    minimum, maximum = np.min(array), np.max(array)
333
    m = (new_max-new_min) / (maximum-minimum)
334
    b = new_min - m* minimum
335
    return m*array + b
336
337
def _inpter2(Eij, k=10):
338
    nx, ny = Eij.shape
339
    
340
    x  = np.linspace(0,nx-1,nx)
341
    y  = np.linspace(0,ny-1,ny)
342
    xq = np.linspace(0,nx-1,nx*k)
343
    yq = np.linspace(0,ny-1,ny*k)
344
    
345
    f = interp2d(x,y,Eij,kind='linear')
346
    
347
    return f(xq,yq)    
348
    
349
def _get_lv2rv_angle(mask):
350
    cx_lv, cy_lv = center_of_mass(mask>1)[:2]
351
    cx_rv, cy_rv = center_of_mass(mask==1)[:2]
352
    phi_angle    = _py_ang([cx_rv-cx_lv, cy_rv-cy_lv], [0, 1])
353
    return phi_angle    
354
    
355
    
356
    
357
### FUNCTIONS TO PLOT THE POLAR MAP 
358
359
import numpy as np
360
import matplotlib as mpl
361
import matplotlib.pyplot as plt
362
from matplotlib.lines import Line2D
363
from copy import deepcopy
364
365
def _write(ax, mu, j, theta_i, i, width=2):
366
    xi, yi = polar2cart(0,  theta_i)
367
    xf, yf = polar2cart(35, theta_i)
368
369
    l = Line2D([40-xi,40-xf], [40-yi,40-yf], color='black', linewidth=width)
370
    ax.add_line(l)
371
    xi, yi = polar2cart(30, theta_i + 2*np.pi/12)
372
    ax.text(40-xi-.3, 40-yi, '%d' %(mu[j][i]), weight='bold', fontsize=14)
373
    
374
def write(ax, mu, j, width=2):
375
    if j > 1:
376
        for i in range(6):
377
            theta_i = 2*np.pi - i*60*np.pi/180 + 2*60*np.pi/180
378
            _write(ax, mu, j, theta_i, i)
379
    if j == 1:
380
        for i in range(4):
381
            theta_i = i*90*np.pi/180 - 45*np.pi/180
382
            _write(ax, mu, j, theta_i, i)
383
    if j ==0:
384
        ax.text(40-.3, 40, '%d' %(mu[j][0]), weight='bold', fontsize=14)
385
386
def plot_bullseye(data,mu,vmin=None,vmax=None, savepath=None,cmap='RdBu_r', label='GPRS (%)', 
387
                  std=None,cbar=False,color='white', fs=20, xshift=0, yshift=0, ptype='mesh',frac=False):
388
    
389
    rho     = np.arange(0,4,4.0/data.shape[1]);
390
    Theta   = np.deg2rad(range(data.shape[0]))
391
    [th, r] = np.meshgrid(Theta, rho);
392
393
    fig, ax = plt.subplots(figsize=(6,6))
394
    #fig.subplots_adjust(left=0,right=1,bottom=0,top=1)
395
    #ax.axis('tight') creates errors 
396
    #ax.axis('off')
397
    if ptype == 'mesh':
398
        im = ax.pcolormesh(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
399
                           vmin=vmin,vmax=vmax,cmap=cmap,shading='gouraud')
400
    else:
401
        im = ax.contourf(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
402
                           vmin=vmin,vmax=vmax,cmap=cmap,shading='gouraud')
403
    if cbar:
404
        cbar = plt.colorbar(im, cax=fig.add_axes([0.15, -0.03, 0.7, 0.05]), orientation='horizontal')
405
406
        new_ticks = []
407
        new_ticks_labels = []
408
        for i,tick in enumerate(cbar.ax.get_xticks()):
409
            if i % 2 == 0:
410
                new_ticks.append(np.round(tick))
411
                new_ticks_labels.append(str(int(np.round(tick))))
412
413
        cbar.set_ticks(new_ticks);
414
        cbar.set_ticklabels(new_ticks_labels);
415
416
        # override if vmin is provided, assume vmax is provided too for now
417
        if vmin is not None:
418
            cbar.set_ticks([vmin, (vmax+vmin)/2.0, vmax]);
419
            cbar.set_ticklabels(['%d'%(i) for i in [vmin, (vmax+vmin)/2.0, vmax]]);
420
        cbar.ax.tick_params(labelsize=18)
421
        cbar.set_label(label, fontsize=26, weight='bold')
422
423
    ax.axis('off')
424
    if std is not None:
425
        draw_circle_group(ax,100*np.array(mu),100*np.array(std))
426
    if frac:
427
        draw_circle_frac(ax,np.array(mu), color=color)
428
    else:
429
        draw_circle(ax,100*np.array(mu), color=color, fs=fs, xshift=xshift, yshift=yshift)
430
    if savepath is not None:
431
        if not cbar:
432
            plt.tight_layout()
433
        plt.savefig(savepath, dpi=600)
434
    plt.show()
435
    
436
def plot_bullseye_ratio(data,mu,vmin=None,vmax=None, savepath=None,cmap='RdBu_r', label='GPRS (%)', 
437
                  std=None,cbar=False,color='white',ptype='mesh',frac=False):
438
    
439
    rho     = np.arange(0,4,4.0/data.shape[1]);
440
    Theta   = np.deg2rad(range(data.shape[0]))
441
    [th, r] = np.meshgrid(Theta, rho);
442
443
    fig, ax = plt.subplots(figsize=(6,6))
444
445
    if ptype == 'mesh':
446
        im = ax.pcolormesh(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
447
                           vmin=vmin,vmax=vmax,cmap=cmap,shading='gouraud')
448
    else:
449
        im = ax.contourf(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
450
                           vmin=vmin,vmax=vmax,cmap=cmap,shading='gouraud')
451
    cbar = plt.colorbar(im, cax=fig.add_axes([0.15, -0.03, 0.7, 0.05]), orientation='horizontal')
452
453
    draw_circle_error(ax)
454
    ax.axis('off')
455
    if savepath is not None:
456
        if not cbar:
457
            plt.tight_layout()
458
        plt.savefig(savepath, dpi=600)
459
    plt.show()
460
    
461
def plot_bullseye_error(data,mu,vmin=None,vmax=None, savepath=None,cmap='RdBu_r', label='GPRS (%)',n=5):
462
    
463
    rho     = np.arange(0,4,4.0/data.shape[1]);
464
    Theta   = np.deg2rad(range(data.shape[0]))
465
    [th, r] = np.meshgrid(Theta, rho);
466
467
    fig, ax = plt.subplots(figsize=(6,6))
468
469
    levels = np.linspace(vmin, vmax, n+1)
470
    im = ax.contourf(r*np.cos(Theta), r*np.sin(Theta), 100*data.T, 
471
                           vmin=vmin,vmax=vmax,cmap=cmap,levels=levels)
472
473
    cbar = plt.colorbar(im, cax=fig.add_axes([0.15, -0.03, 0.7, 0.05]), orientation='horizontal')
474
475
    #ticks = -np.array(range(0,120,20))
476
477
    #cbar.set_ticks(ticks);
478
    #cbar.set_ticklabels(['%d'%(i) for i in ticks]);
479
480
     
481
482
    ax.axis('off')
483
    draw_circle_error(ax)
484
    if savepath is not None:
485
        if not cbar:
486
            plt.tight_layout()
487
        plt.savefig(savepath, dpi=500)
488
    plt.show()
489
490
def draw_circle_error(ax,width=4):
491
492
    circle1 = plt.Circle((0,0), 1, color='black', fill=False, linewidth=width)
493
    circle2 = plt.Circle((0,0), 2, color='black', fill=False, linewidth=width)
494
    circle3 = plt.Circle((0,0), 3, color='black', fill=False, linewidth=width)
495
    circle4 = plt.Circle((0,0), 4, color='black', fill=False, linewidth=width)
496
497
    ax.add_artist(circle1)
498
    ax.add_artist(circle2)
499
    ax.add_artist(circle3)
500
    ax.add_artist(circle4)
501
    
502
    j = 0
503
    for i in range(6):
504
        theta_i = i*60*np.pi/180 + 60*np.pi/180
505
        xi, yi = polar2cart(2, theta_i)
506
        xf, yf = polar2cart(4, theta_i)
507
        
508
        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
509
        ax.add_line(l)
510
511
    j += 6
512
    for i in range(4):
513
        theta_i = i*90*(np.pi/180) - 45
514
        xi, yi  = polar2cart(1, theta_i)
515
        xf, yf  = polar2cart(2, theta_i)
516
        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
517
        ax.add_line(l)
518
519
def draw_circle_frac(ax, mu, width=4, fs=20, color='white'):
520
    
521
    
522
    
523
    circle1 = plt.Circle((0,0), 1, color='black', fill=False, linewidth=width)
524
    circle2 = plt.Circle((0,0), 2, color='black', fill=False, linewidth=width)
525
    circle3 = plt.Circle((0,0), 3, color='black', fill=False, linewidth=width)
526
    circle4 = plt.Circle((0,0), 4, color='black', fill=False, linewidth=width)
527
528
    ax.add_artist(circle1)
529
    ax.add_artist(circle2)
530
    ax.add_artist(circle3)
531
    ax.add_artist(circle4)
532
    
533
    j = 0
534
    for i in range(6):
535
        theta_i = i*60*np.pi/180 + 60*np.pi/180
536
        xi, yi = polar2cart(2, theta_i)
537
        xf, yf = polar2cart(4, theta_i)
538
        
539
        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
540
        ax.add_line(l)
541
        
542
        xi, yi = polar2cart(3.5, theta_i + 2*np.pi/12)
543
        ax.text(xi-.3, yi, '%.2f' %(mu[j]), weight='bold', fontsize=fs, color=color);
544
        xi, yi = polar2cart(2.5, theta_i + 2*np.pi/12)
545
        ax.text(xi-.3, yi, '%.2f' %(mu[j+6]), weight='bold', fontsize=fs, color=color); j += 1
546
        
547
    j += 6
548
    LABELS = ['ANT', 'SEPT', 'INF', 'LAT']
549
    for i in range(4):
550
        theta_i = i*90*np.pi/180 - 45 
551
        xi, yi = polar2cart(1, theta_i)
552
        xf, yf = polar2cart(2, theta_i)
553
        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
554
        ax.add_line(l)
555
        
556
        xi, yi = polar2cart(1.5, theta_i + 2*np.pi/8)
557
558
        ax.text(xi-.3, yi, '%.2f' %(mu[j]), weight='bold', fontsize=fs, color=color); j += 1;
559
        xi, yi = polar2cart(5, theta_i + 2*np.pi/8)
560
561
    ax.text(0-.3, 0-.3, '%.2f' %(mu[j]), weight='bold', fontsize=fs, color=color)
562
    
563
def draw_circle(ax, mu, width=4, fs=15, xshift=0, yshift=0, color='white'):
564
    
565
    
566
    
567
    circle1 = plt.Circle((0,0), 1, color='black', fill=False, linewidth=width)
568
    circle2 = plt.Circle((0,0), 2, color='black', fill=False, linewidth=width)
569
    circle3 = plt.Circle((0,0), 3, color='black', fill=False, linewidth=width)
570
    circle4 = plt.Circle((0,0), 4, color='black', fill=False, linewidth=width)
571
572
    ax.add_artist(circle1)
573
    ax.add_artist(circle2)
574
    ax.add_artist(circle3)
575
    ax.add_artist(circle4)
576
    
577
    j = 0
578
    for i in range(6):
579
        theta_i = i*60*np.pi/180 + 60*np.pi/180
580
        xi, yi = polar2cart(2, theta_i)
581
        xf, yf = polar2cart(4, theta_i)
582
        
583
        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
584
        ax.add_line(l)
585
        
586
        xi, yi = polar2cart(3.5, theta_i + 2*np.pi/12)
587
        ax.text(xi-.4-xshift, yi-yshift, '%d' %(mu[j]), weight='bold', fontsize=fs, color=color);
588
        xi, yi = polar2cart(2.5, theta_i + 2*np.pi/12)
589
        ax.text(xi-.4-xshift, yi-yshift, '%d' %(mu[j+6]), weight='bold', fontsize=fs, color=color); j += 1
590
        
591
    j += 6
592
    LABELS = ['ANT', 'SEPT', 'INF', 'LAT']
593
    for i in range(4):
594
        theta_i = i*90*np.pi/180  + 45*np.pi/180
595
        xi, yi = polar2cart(1, theta_i)
596
        xf, yf = polar2cart(2, theta_i)
597
        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
598
        ax.add_line(l)
599
        
600
        xi, yi = polar2cart(1.5, theta_i + 2*np.pi/8)
601
602
        ax.text(xi-.4-xshift, yi-yshift, '%d' %(mu[j]), weight='bold', fontsize=fs, color=color); j += 1;
603
        xi, yi = polar2cart(5, theta_i + 2*np.pi/8)
604
605
    ax.text(-.4-xshift, 0-yshift, '%d' %(mu[j]), weight='bold', fontsize=fs, color=color)
606
  
607
608
def draw_circle_group(ax, mu, std, width=4, fs=14, color='white'):
609
    
610
        
611
    circle1 = plt.Circle((0,0), 1, color='black', fill=False, linewidth=width)
612
    circle2 = plt.Circle((0,0), 2, color='black', fill=False, linewidth=width)
613
    circle3 = plt.Circle((0,0), 3, color='black', fill=False, linewidth=width)
614
    circle4 = plt.Circle((0,0), 4, color='black', fill=False, linewidth=width)
615
616
    ax.add_artist(circle1)
617
    ax.add_artist(circle2)
618
    ax.add_artist(circle3)
619
    ax.add_artist(circle4)
620
    
621
    j = 0
622
    for i in range(6):
623
        theta_i = i*60*np.pi/180 + 60*np.pi/180
624
        xi, yi = polar2cart(2, theta_i)
625
        xf, yf = polar2cart(4, theta_i)
626
        
627
        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
628
        ax.add_line(l)
629
        
630
        xi, yi = polar2cart(3.5, theta_i + 2*np.pi/12)
631
        ax.text(xi-.6, yi, '%d(%d)' %(mu[j],std[j]), weight='bold', fontsize=fs, color=color);
632
        xi, yi = polar2cart(2.5, theta_i + 2*np.pi/12)
633
        ax.text(xi-.6, yi, '%d(%d)' %(mu[j+6],std[j+6]), weight='bold', fontsize=fs, color=color); j += 1
634
        
635
    j += 6
636
    LABELS = ['ANT', 'SEPT', 'INF', 'LAT']
637
    for i in range(4):
638
        theta_i = i*90*np.pi/180 
639
        xi, yi = polar2cart(1, theta_i)
640
        xf, yf = polar2cart(2, theta_i)
641
        l = Line2D([xi,xf], [yi,yf], color='black', linewidth=width)
642
        ax.add_line(l)
643
        
644
        xi, yi = polar2cart(1.5, theta_i + 2*np.pi/8)
645
646
        ax.text(xi-.6, yi-0.1, '%d(%d)' %(mu[j],std[j]), weight='bold', fontsize=fs, color=color); j += 1;
647
        xi, yi = polar2cart(5, theta_i + 2*np.pi/8)
648
649
    ax.text(0-.3, 0-.2, '%d(%d)' %(mu[j],std[j]), weight='bold', fontsize=fs, color=color)
650
    
651
def polar2cart(r, theta):
652
    x = r * np.cos(theta)
653
    y = r * np.sin(theta)
654
    return x, y