|
a |
|
b/cmac/aux_strain.py |
|
|
1 |
|
|
|
2 |
from scipy.ndimage.measurements import center_of_mass |
|
|
3 |
from skimage.measure import find_contours |
|
|
4 |
import numpy as np |
|
|
5 |
|
|
|
6 |
|
|
|
7 |
def label_to_tags(label_3d, label_id=2): |
|
|
8 |
Ck = [] |
|
|
9 |
for z in range(label_3d.shape[-1]): |
|
|
10 |
try: |
|
|
11 |
ck = tag_contour((label_3d[:,:,z].T==label_id)*1. ) |
|
|
12 |
ck = np.concatenate((ck,np.zeros((24,3,1))+z),-1) |
|
|
13 |
Ck += [ck] |
|
|
14 |
except: |
|
|
15 |
continue |
|
|
16 |
return np.stack(Ck) |
|
|
17 |
|
|
|
18 |
def label_to_tags2(contours_dict, K=24, R=5, centroid=None): |
|
|
19 |
Ck = [] |
|
|
20 |
for z in contours_dict.keys(): |
|
|
21 |
ck = tag_contour2(contours_dict[z], K=K, R=R, centroid=centroid) |
|
|
22 |
ck = np.concatenate((ck,np.zeros((K,R,1))+z),-1) |
|
|
23 |
Ck += [ck] |
|
|
24 |
return np.stack(Ck) |
|
|
25 |
|
|
|
26 |
def tag_contour(myocardial_mask_2d, K=24, R=3): |
|
|
27 |
"""Generate polar grid using myocardial mass mask. |
|
|
28 |
""" |
|
|
29 |
Cx, Cy = center_of_mass(myocardial_mask_2d) |
|
|
30 |
|
|
|
31 |
contours_endo, contours_epi = find_contours(myocardial_mask_2d,0.8) |
|
|
32 |
|
|
|
33 |
# cartesian coordinates (centered) of endo/epi contours |
|
|
34 |
x_endo, y_endo = contours_endo[:,0], contours_endo[:,1] |
|
|
35 |
x_epi, y_epi = contours_epi[:,0], contours_epi[:,1] |
|
|
36 |
|
|
|
37 |
# polar coordinates of endo/epi contours |
|
|
38 |
phi_endo = np.rad2deg(np.arctan2(y_endo-Cy, x_endo-Cx))+180 |
|
|
39 |
phi_epi = np.rad2deg(np.arctan2(y_epi-Cy, x_epi-Cx))+180 |
|
|
40 |
rho_endo = ((x_endo-Cx)**2 + (y_endo-Cy)**2)**0.5 |
|
|
41 |
rho_epi = ((x_epi-Cx)**2 + (y_epi-Cy)**2)**0.5 |
|
|
42 |
|
|
|
43 |
IDX = [np.array_split(np.argsort(phi_endo),K)[k][0] for k in range(K)] |
|
|
44 |
|
|
|
45 |
ck = [] |
|
|
46 |
for i in IDX: |
|
|
47 |
d = (x_endo[i]-x_epi)**2+(y_endo[i]-y_epi)**2 |
|
|
48 |
dmin_i = np.argmin(d) |
|
|
49 |
|
|
|
50 |
rhos = np.linspace(rho_endo[i],rho_epi[dmin_i],R) |
|
|
51 |
xk = [rhos[k] * np.cos(np.deg2rad(phi_endo[i]-180)) for k in range(R)] + Cx |
|
|
52 |
yk = [rhos[k] * np.sin(np.deg2rad(phi_endo[i]-180)) for k in range(R)] + Cy |
|
|
53 |
ck += [np.stack((yk,xk),-1)] |
|
|
54 |
|
|
|
55 |
ck = np.stack(ck,0) |
|
|
56 |
|
|
|
57 |
return ck |
|
|
58 |
|
|
|
59 |
def tag_contour2(contours, phi_regionIDs, K=24, R=3, centroid=None): |
|
|
60 |
"""Generate polar grid using endocardial and epicardial contours. |
|
|
61 |
""" |
|
|
62 |
get_cx_cy = lambda contour : (contour.max(axis=0) + contour.min(axis=0)) / 2 |
|
|
63 |
|
|
|
64 |
contours_endo, contours_epi = contours['endocardium'], contours['epicardium'] |
|
|
65 |
|
|
|
66 |
if centroid is None: |
|
|
67 |
Cx, Cy = get_cx_cy(contours_endo) |
|
|
68 |
else: |
|
|
69 |
Cx, Cy = centroid |
|
|
70 |
|
|
|
71 |
# cartesian coordinates (centered) of endo/epi contours |
|
|
72 |
x_endo, y_endo = contours_endo[:,0], contours_endo[:,1] |
|
|
73 |
x_epi, y_epi = contours_epi[:,0], contours_epi[:,1] |
|
|
74 |
|
|
|
75 |
# polar coordinates of endo/epi contours |
|
|
76 |
phi_endo = np.rad2deg(np.arctan2(y_endo-Cy, x_endo-Cx))+180 |
|
|
77 |
phi_epi = np.rad2deg(np.arctan2(y_epi-Cy, x_epi-Cx))+180 |
|
|
78 |
rho_endo = ((x_endo-Cx)**2 + (y_endo-Cy)**2)**0.5 |
|
|
79 |
rho_epi = ((x_epi-Cx)**2 + (y_epi-Cy)**2)**0.5 |
|
|
80 |
|
|
|
81 |
IDX = [np.array_split(np.argsort(phi_endo),K)[k][0] for k in range(K)] |
|
|
82 |
|
|
|
83 |
|
|
|
84 |
ck = [] |
|
|
85 |
ck_regionIDs = [] |
|
|
86 |
for i in IDX: |
|
|
87 |
|
|
|
88 |
d = (x_endo[i]-x_epi)**2+(y_endo[i]-y_epi)**2 |
|
|
89 |
dmin_i = np.argmin(d) |
|
|
90 |
|
|
|
91 |
rhos = np.linspace(rho_endo[i],rho_epi[dmin_i],R) |
|
|
92 |
xk = [rhos[k] * np.cos(np.deg2rad(phi_endo[i]-180)) for k in range(R)] + Cx |
|
|
93 |
yk = [rhos[k] * np.sin(np.deg2rad(phi_endo[i]-180)) for k in range(R)] + Cy |
|
|
94 |
ck += [np.stack((xk,yk),-1)] |
|
|
95 |
ck_regionIDs += [[phi_regionIDs[i]]*R] |
|
|
96 |
|
|
|
97 |
ck = np.stack(ck,0) |
|
|
98 |
ck_regionIDs = np.stack(ck_regionIDs,0) |
|
|
99 |
return ck, ck_regionIDs |
|
|
100 |
|
|
|
101 |
|
|
|
102 |
|
|
|
103 |
def calculate_circumferential_strain(coords_batch, wall_index, use_linear_strain=False): |
|
|
104 |
# batch x time x 2 x 24 |
|
|
105 |
midwall_points = coords_batch[:,:,:, wall_index::7] # get point index 3 for every radial |
|
|
106 |
# print(midwall_points.shape) |
|
|
107 |
|
|
|
108 |
# we will have to calculate the strain between every points |
|
|
109 |
|
|
|
110 |
points_arr = np.split(midwall_points, 24, axis=3) |
|
|
111 |
|
|
|
112 |
# strain formula: ((l^2/L^2)-1) / 2 --> l^2 = x^2 + y^2 |
|
|
113 |
# with x and y is the difference between x and y coords of 2 points |
|
|
114 |
ccs = [] |
|
|
115 |
# the cc strain is circular, so we going through all of them and back to point 0 |
|
|
116 |
for r in range(0,len(points_arr)): |
|
|
117 |
# for the last point, calculate between point_r and point_0 |
|
|
118 |
if r+1 == len(points_arr): |
|
|
119 |
cc_diff = np.square(points_arr[r] - points_arr[0]) |
|
|
120 |
else: |
|
|
121 |
cc_diff = np.square(points_arr[r] - points_arr[r+1]) |
|
|
122 |
|
|
|
123 |
# do the sum: x^2 + y^2 |
|
|
124 |
cc_sum = cc_diff[:,:,0] + cc_diff[:,:,1] |
|
|
125 |
|
|
|
126 |
if use_linear_strain: |
|
|
127 |
# use L instead of L^2 |
|
|
128 |
cc_sum = np.sqrt(cc_sum) |
|
|
129 |
|
|
|
130 |
cc_sum_ed = cc_sum[:,0] |
|
|
131 |
|
|
|
132 |
# do the strain calculation |
|
|
133 |
partial_cc = cc_sum/cc_sum_ed[:, np.newaxis] |
|
|
134 |
if use_linear_strain: |
|
|
135 |
partial_cc = (partial_cc - 1) |
|
|
136 |
else: |
|
|
137 |
partial_cc = (partial_cc - 1) / 2 |
|
|
138 |
|
|
|
139 |
# put the partial_cc in every time frame back together |
|
|
140 |
ccs.append(partial_cc) |
|
|
141 |
# stack the partial_cc for every links together |
|
|
142 |
stacked_ccs = np.stack(ccs, axis=2) |
|
|
143 |
|
|
|
144 |
# calculate the mean cc for every time frame |
|
|
145 |
mid_cc = np.mean(stacked_ccs, axis=2) |
|
|
146 |
# print(mid_cc.shape) |
|
|
147 |
# print(mid_cc[0][0:5]) |
|
|
148 |
return mid_cc |
|
|
149 |
|
|
|
150 |
def calculate_radial_strain(coords_batch, use_linear_strain=False): |
|
|
151 |
""" |
|
|
152 |
Calculate rr strain for a batch of image sequences |
|
|
153 |
flattened_coords => [batch_size, nr_frames, 2, 168] |
|
|
154 |
""" |
|
|
155 |
# point 0 is epi, point 6 is endo, do this for all the 'radials' |
|
|
156 |
endo_batch = coords_batch[:, :, :, ::7] |
|
|
157 |
epi_batch = coords_batch[:, :, :, 6::7] |
|
|
158 |
|
|
|
159 |
# batch x time x 2 x 24 radials |
|
|
160 |
diff = (epi_batch - endo_batch) ** 2 |
|
|
161 |
# print('diff', diff.shape) |
|
|
162 |
|
|
|
163 |
# batch x time x 24 sqrdiff |
|
|
164 |
summ = diff[:,:,0,:] + diff[:,:,1,:] # x^2 + y^2 |
|
|
165 |
# print('summ', summ.shape) |
|
|
166 |
|
|
|
167 |
if use_linear_strain: |
|
|
168 |
# use L instead of L^2 |
|
|
169 |
summ = np.sqrt(summ) |
|
|
170 |
|
|
|
171 |
# grab the frame 0 (ED) for all data, and 24 RR strains |
|
|
172 |
summ_ed = summ[:,0,:] |
|
|
173 |
|
|
|
174 |
# division through a certain column, without np.split |
|
|
175 |
# batch x time x 24 rr strains |
|
|
176 |
divv = summ/summ_ed[:,np.newaxis] # this is the trick, add new axis |
|
|
177 |
|
|
|
178 |
if use_linear_strain: |
|
|
179 |
rr_strains = divv - 1 |
|
|
180 |
else: |
|
|
181 |
rr_strains = (divv - 1) / 2 |
|
|
182 |
|
|
|
183 |
rr_strains = np.mean(rr_strains, axis=2) |
|
|
184 |
|
|
|
185 |
# batch x time x strain |
|
|
186 |
rr_strains = np.expand_dims(rr_strains, axis=2) |
|
|
187 |
return rr_strains |