Tính toán phân suất tống máu

Một phần của tài liệu Xử lí hình ảnh siêu âm tim 3d (Trang 54)

2. Mục tiêu nghiên cứu

3.3Tính toán phân suất tống máu

Google Colab là một dich vụ đám mây miễn phí, hiện nay có hỗ trợ GPU (Tesla K80) và TPU (TPUv2). Do được phát triển dựa trên Jupiter Notebook nên việc sử dụng Google Colab cũng tương tự như việc sử dụng Jupyter Notebook. Google Colab là một công cụ lý tưởng để chúng ta rèn luyện kĩ năng lập trình với ngôn ngữ Python thông qua các thư viện của deep learning. Google Colab cài đặt sẵn cho chúng ta những thư viện rất phổ biến trong nghiên cứu Deep Learning như PyTorch, TensorFlow, Keras và OpenCV.

3.3.2 Xử lí dữ liệu đầu vào

Dữ liệu đầu vào bao gồm ảnh 4 ảnh mặt nạ: Ảnh siêu âm tim 4 buồng cuối thì tâm trương và tâm thu, ảnh siêu âm tim 2 buồng cuối thì tâm trương và tâm thu. Ngoài ra còn 2 tệp dữ liệu DICOM của hình ảnh siêu âm tim 4 buồng và 2 buồng.

41

Hình 3.8: Hình ảnh các tệp được tải lên

Các thư viện sử dụng cho chương trình:

Thư viện Pydicom để có thể đọc dữ liệu từ các file định dạng DICOM Thư viện OpenCV để đọc và xử lý dữ liệu ảnh

Thư viện Numpy để tính toán

Thư viện Matplotlib để in ra hình ảnh import pydicom as dicom

import matplotlib.pylab as plt import cv2

import numpy as np

from sympy import symbols, Eq, solve import warnings

warnings.filterwarnings("ignore")

42

Bước 1: Đọc dữ liệu từ các file dicom và mask sau đó chuyển thành ảnh xám.

Sử dụng thư viện pydicom đểđọc dữ liệu từfile dicom, opencv để đọc dữ liệu từ ảnh mặt nạ. Lấy khung hình tương ứng với ảnh mặt nạ sau đó chuyển sang ảnh xám (Hình 3.9).

Hình 3.9: Hình ảnh siêu âm được chuyển sang ảnh xám

Bước 2: Tìm đường bao từ ảnh mặt nạ, xác định các toạ độ của bounding box (Hình 3.10).

Sử dụng thư viện OpenCV chuyển từ ảnh mặt nạ thành đường bao (contour), sau đó tìm ra toạ độ các góc của bounding box.

43

Hình 3.10: Chuyển ảnh mặt nạ thành đường bao

Bước 3: Vẽ đường thẳng Ld (Hình 3.11).

Tìm toạ độ điểm chính giữa của bounding box, tìm điểm A có tung độ cao nhất tại đường bao. Tìm điểm B tại đáy của đường bao sao cho hoành độ bằng với hoành độ tại điểm chính giữa của bounding box.

44

Hình 3.11: Vẽ đường thẳng Ld

Bước 4: Vẽ n đường thẳng song song, cách đều và vuông góc với đường Ld (Hình

3.12).

Tính độ dài đường thằng Ld, chia cho n+1 đường thẳng song song cần vẽ tính được khoảng cách đơn vị h giữa hai đường thằng song song liền nhau.

Tìm phương trình đường thẳng Ld.

Tìm phương trình đường thẳng D_i vuông góc với Ld và cách điểm A một khoảng bằng i x h (i = 1,n).

Tìm điểm 2 điểm L_i, R_i thuộc nửa trái và phải của đường bao sao cho nằm trên đường thẳng D_i.

Nối điểm L_i và R_i thu được n đường thẳng song song cách đều nhau và vuông góc với đường Ld.

45

Hình 3.12: Vẽ n đường thẳng song song, cách đều và vuông góc với đường Ld

Bước 5: Đo độ dài đường thẳng Ld và n đường thẳng song song.

Viết phương trình đường thẳng đi qua hai điểm thông qua vector pháp tuyến: def find_line_equation(left_point,right_point): #ax+by+c=0 a = -(right_point[1]-left_point[1]) b = right_point[0]-left_point[0] #(a,b): VTPT c = -(a*left_point[0]+b*left_point[1]) return [a,b,c] Lấy các toạđộ góc của bounding box def get_order_points(pts): # first - top-left, # second - top-right # third - bottom-right # fourth - bottom-left

46 rect = np.zeros((4, 2), dtype="int")

# top-left point will have the smallest sum # bottom-right point will have the largest sum s = pts.sum(axis=1)

rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)]

# top-right point will have the smallest difference # bottom-left will have the largest difference diff = np.diff(pts, axis=1)

rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)]

return rect

Chia đường bao thành 4 phần phục vụ cho việc tìm điểm trong đường bao thoả mãn phương trình cho trước:

defdetached_contour(contours, top_point, bot_point,center_point): arg_top = np.argwhere(contours[:,1] < center_point[1]+1)

arg_bot = np.argwhere(contours[:,1] > center_point[1]+1) contour_point_top = contours[arg_top].reshape(-1,2) contour_point_bot = contours[arg_bot].reshape(-1,2)

arg_top_left = np.argwhere(contour_point_top[:,0] < top_point[0]+1) arg_top_right = np.argwhere(contour_point_top[:,0] > top_point[0]+1) contour_top_left = contour_point_top[arg_top_left].reshape(-1,2) contour_top_right = contour_point_top[arg_top_right].reshape(-1,2)

arg_bot_left = np.argwhere(contour_point_bot[:,0] < bot_point[0]+1) arg_bot_right = np.argwhere(contour_point_bot[:,0] > bot_point[0]+1)

47 contour_bot_left = contour_point_bot[arg_bot_left].reshape(-1,2)

contour_bot_right = contour_point_bot[arg_bot_right].reshape(-1,2)

return contour_top_left, contour_top_right, contour_bot_left, contour_bot_right Vẽ đường thẳng nối hai điểm:

def draw_line_with_2_point(output_image, point_1, point_2):

cv2.line(output_image, tuple(point_1), tuple(point_2), (0, 0, 255), 1) # cv2.circle(output_image, tuple(point_1), 3, (255, 0, 0), 1)

# cv2.circle(output_image, tuple(point_2), 3, (255, 0, 0), 1) Tìm đường chính giữa của bounding box:

def find_center_line_points(contours):

commodity_rect = cv2.minAreaRect(contours[0])

bounding_rect_points = np.array(cv2.boxPoints(commodity_rect), dtype=np.int )

tl, tr, br, bl = get_order_points(bounding_rect_points)

left_point = (tl + bl) // 2 right_point = (tr + br) // 2

center_point = (left_point + right_point) //2

return left_point, right_point, center_point Tìm khoảng cách giữa hai điểm:

def find_distance_from_2_point(point_1, point_2):

return np.sqrt((point_1[0] - point_2[0])*(point_1[0] - point_2[0]) + (point_1[1] - point_2[1])*(point_1[1] - point_2[1]))

48 def find_volume(Ld, ai, bi, num_parallbel_line):

return np.pi*Ld*np.dot(ai.T,bi) / (4*num_parallbel_line)

Tìm phương trình đường thẳng vuông góc và cách một khoảng cho trước: def find_eq_line_from_distance_new(line_eq_Ld, top_point, distance): a ,b = -line_eq_Ld[1], line_eq_Ld[0]

c = distance*np.sqrt(a*a+b*b) - a*top_point[0] - b*top_point[1] return [a,b,c]

Tìm hai điểm thuộc hai phía trái phải của đường bao thỏa mãn nằm phương trình cho trước

def find_point_in_line3(line_equation, contour_point_left, contour_point_right):

result_left = np.array(np.abs(line_equation[0]*contour_point_left[:,0] + line_eq uation[1]*contour_point_left[:,1] + line_equation[2]))

argmin_1 = np.argmin(result_left)

point_1 = contour_point_left[argmin_1,:]

result_right = np.array(np.abs(line_equation[0]*contour_point_right[:,0] + line _equation[1]*contour_point_right[:,1] + line_equation[2]))

argmin_2 = np.argmin(result_right)

point_2 = contour_point_right[argmin_2,:]

return point_1,point_2

Vẽ n đường thẳng song song, cách đều và vuông góc với đường thẳng cho trước: def draw_n_line_parallel_new(output_image, eq_line_Ld, top_point, bot_point, c ontour_point_left, contour_point_right, n_line=20):

length_n_parallbel_lines = []

dis_from_top2bot = find_distance_from_2_point(top_point,bot_point) distance_unit = dis_from_top2bot / (n_line +1)

49 eq_line = find_eq_line_from_distance_new(eq_line_Ld, top_point, distance_u nit*i)

point_left_top, point_right_top = find_point_in_line3(eq_line,contour_point_l eft, contour_point_right)

length_line1 = find_distance_from_2_point(point_left_top, point_right_top) length_n_parallbel_lines.append(length_line1)

draw_line_with_2_point(output_image,point_left_top,point_right_top) return length_n_parallbel_lines

Hàm chương trình chính nhận đầu vào là đường dẫn của dicom, mask, số slice tương ứng, số đường song song cần vẽ:

def find_Ld_and_length_parallbel_new(dcm_path, mask_path, num_slice = 40, n um_parallbel_line = 18, isA4C = True, isDiastole=True):

#Read file

ds = dicom.dcmread(dcm_path)

gray_img = cv2.cvtColor(ds.pixel_array[num_slice], cv2.COLOR_BGR2GRA Y)

img = cv2.cvtColor(ds.pixel_array[num_slice], cv2.COLOR_RGB2GRAY) mask_img = cv2.imread(mask_path)

mask_img = cv2.cvtColor(mask_img, cv2.COLOR_RGB2GRAY)

#Find and draw contour

ret, thresh = cv2.threshold(mask_img, 127, 255, 0)

contours, hier = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APP ROX_NONE)

output_image = img.copy()

commodity_rect = cv2.minAreaRect(contours[0])

bounding_rect_points = np.array(cv2.boxPoints(commodity_rect), dtype=np.int )

cv2.drawContours(output_image, contours, -1, (0, 255, 0), 1)

50 left_point, right_point, center_point = find_center_line_points(contours)

contour = contours[0].reshape(-1,2) y_min = np.min(contour[:,1]) argmin_y = np.argmin(contour[:,1]) x_ymin = contour[argmin_y,0] top_point = [x_ymin,y_min]

arg_bot = np.argwhere(contour[:,0] == center_point[0]) bot_point = contour[arg_bot[0]][0]

cv2.line(output_image, tuple(top_point), tuple(bot_point), (0, 0, 255), 1) Ld = find_distance_from_2_point(top_point, bot_point)

line_equation_Ld_line = find_line_equation(top_point,bot_point)

#Draw n parallbel line

arg_top_point_1 = np.where(contour[:,0] == top_point[0]) arg_top_point_2 = np.where(contour[:,1] == top_point[1]) arg_top = np.intersect1d(arg_top_point_1,arg_top_point_2) arg_bot_point_1 = np.where(contour[:,0] == bot_point[0]) arg_bot_point_2 = np.where(contour[:,1] == bot_point[1]) arg_bot = np.intersect1d(arg_bot_point_1,arg_bot_point_2) contour_left = contour[arg_top[0]:arg_bot[0]]

contour_right = contour[arg_bot[0]:]

num_parallbel_line = num_parallbel_line + 1

length_n_parallbel_lines = draw_n_line_parallel_new(output_image, line_equat ion_Ld_line, top_point, bot_point, contour_left, contour_right, n_line=num_para llbel_line)

#show and save image plt.figure(figsize=(16,8)) plt.axis('off')

plt.imshow(output_image,cmap=plt.cm.bone) if isA4C:

51 else: sub_name_1 = 'A2C' if isDiastole: sub_name_2 = 'diastole' else: sub_name_2 = 'systolic'

save_name = "result_" + sub_name_1 + "_" + sub_name_2 + "_" + str(num_slic e)

plt.savefig(save_name)

return Ld, length_n_parallbel_lines

Chương trình chính, cập nhật dữ liệu đầu vào : num_parallbel_line = 20 A4C_discom_path = "/content/IM_0002_A4C" A4C_diastole_mask_path = "/content/1_mask_A4C_D_3_4.jpg" number_slice_A4C_diastole = 2 A4C_systolic_mask_path = "/content/1_mask_A4C_S_33_34.jpg" number_slice_A4C_systolic = 32 A2C_discom_path = "/content/IM_0001_A2C" A2C_diastole_mask_path = "/content/1_mask_A2C_D_33_34.jpg" number_slice_A2C_diastole = 32 A2C_systolic_mask_path = "/content/1_mask_A2C_S_17_18.jpg" number_slice_A2C_systolic = 16 Thì tâm trương:

Ld_A4C_diastole, ai_A4C_diastole = find_Ld_and_length_parallbel_new(A4C_ discom_path,

A4C_diastole_mask_path, number_slice_A4C_diastole,

52 num_parallbel_line - 1,

isA4C = True, isDiastole=True)

d_A2C_diastole, bi_A2C_diastole = find_Ld_and_length_parallbel_new(A2C_di scom_path,

A2C_diastole_mask_path, number_slice_A2C_diastole, num_parallbel_line - 1,

isA4C = False, isDiastole=True) ai_A4C_diastole = np.array(ai_A4C_diastole)

bi_A4C_diastole = np.array(bi_A2C_diastole)

Vd = find_volume(Ld_A4C_diastole,ai_A4C_diastole,bi_A4C_diastole, num_pa rallbel_line)

Thì tâm thu:

Ld_A4C_systolic, ai_A4C_systolic = find_Ld_and_length_parallbel_new(A4C_ discom_path,

A4C_systolic_mask_path, number_slice_A4C_systolic, num_parallbel_line - 1,

isA4C = True, isDiastole=False)

Ld_A2C_systolic, bi_A2C_systolic = find_Ld_and_length_parallbel_new(A2C_ discom_path,

A2C_systolic_mask_path, number_slice_A2C_systolic, num_parallbel_line - 1,

isA4C = False, isDiastole= False) ai_A4C_systolic = np.array(ai_A4C_systolic)

bi_A4C_systolic = np.array(bi_A2C_systolic)

Vs = find_volume(Ld_A4C_systolic,ai_A4C_systolic,bi_A4C_systolic, num_par allbel_line)

53 EF = 100*(Vd - Vs)/Vd

print('Phân suất tống máu {0}%'.format(round(EF,2))) Phân suất tống máu 32.57%

54

KẾT QUẢ VÀ THẢO LUẬN 4.1 Kết quả thực nghiệm

Theo công thức Simpson (2.2) ta cần tính toán thể tích ở cuối thì tâm thu và cuối thì tâm trương để tính toán ra được phân suất tổng máu EF.

Thể tích cuối thì tâm trương:

𝑉𝑉𝑑𝑑 =𝜋𝜋4� 𝑎𝑎𝑖𝑖𝑑𝑑𝑎𝑎𝑖𝑖𝑑𝑑𝐿𝐿𝑛𝑛𝑑𝑑 𝑛𝑛

𝑖𝑖=1

(4.1)

Thể tích cuối thì tâm thu:

𝑉𝑉𝑠𝑠 =𝜋𝜋4� 𝑎𝑎𝑖𝑖𝑠𝑠𝑎𝑎𝑖𝑖𝑠𝑠𝐿𝐿𝑛𝑛𝑠𝑠 𝑛𝑛

𝑖𝑖=1

(4.2)

Từ đây ta có giá trị phân suất tống máu:

𝐸𝐸𝐹𝐹 = 100% ×𝑉𝑉𝑑𝑑𝑉𝑉− 𝑉𝑉𝑠𝑠

𝑑𝑑

(4.3)

55

Hình 4.1: Hình ảnh siêu âm tim A4C thì cuối tâm trương sau khi được bao viền

56

Hình 4.3: Hình ảnh siêu âm tim A4C thì cuối tâm thu sau khi được bao viền

57

Hình 4.5: Hình ảnh kết quả hiển thị trên công cụ Colab của google Bảng 4.1: Bảng so sánh các kết quả với kết quả của máy siêu âm

n=10 n=20 n=30 n=40 Máy siêu âm (n=20) EF (%) 43.41 43.0 42.67 42.56 42.0

Tiếp tục thực nghiệm từ kết quả siêu âm của 4 bệnh nhân khác nhau. Kết quả thu được từ máy siêu âm Epiq của Philips, đầu dò siêu âm tim X5-1:

58

Hình 4.7: Hình ảnh siêu âm tim bệnh nhân 2 và kết quả phân suất thông máu EF

59

Hình 4.9: Hình ảnh siêu âm tim bệnh nhân 4 và kết quả phân suất thông máu EF

Thu thập dữ liệu của 4 bệnh nhân trên để xử lí ta có bảng thông số dữ liệu:

Bảng 4.2: Kết quả thực nghiệm trên 4 bệnh nhân và của máy siêu âm

BN 1 BN 2 BN 3 BN 4 Siêu âm 52.2 40.5 30.4 43.5 n=40 53.56 38.55 32.57 44.01 n=30 53.88 38.98 32.98 44.51 n=20 54.23 39.47 33.12 45.33 n=10 54.41 39.68 33.45 45.67 Từ bảng 4.2 ta có nhận xét sau:

- Các kết quả thu được từ Colab khi thay đổi giá trị n gần như sai khác không đáng kể. Nhìn vào bảng 4.3 ta thấy đối với từng bệnh nhân khi thay đổi giá trị n thì EF không thay đổi nhiều. Đối với BN1 sai số lớn nhất só với gái trị trung bình là 0,8%

Bảng 4.3: Phần trăm sai số giữa các giá trị đo n với giá trị trung bình

BN 1 BN 2 BN 3 BN 4 Trung bình 54,02 39,17 33,03 44,88 n=40 0,8% 1,6% 1,4% 1,9% n=30 0,3% 0,5% 0,2% 0,8% n=20 0,4% 0,8% 0,3% 1,5% n=10 0,7% 1,3% 1,3% 1,8%

60

Bảng 4.4: Phần trăm sai số giữa các phép đo với giá trị siêu âm

BN 1 BN 2 BN 3 BN 4

n=40 2,0% 4,8% 7,1% 1,2%

n=30 3,2% 3,7% 8,5% 2,3%

n=20 3,8% 2,5% 8,9% 4,2%

n=10 4,2% 2,0% 10,0% 5,0%

- Khi so sánh kết quả các kết quả thực nghiệm với máy siêu âm ta nhận thấy kết quả thực nghiệm khá tương đồng với kết quả siêu âm. Nhìn vào bảng 4.4 ta thấy phần trăm sai số là nhỏ và không làm ảnh hưởng đến việc chẩn đoán lâm sàng của bác sĩ. Vì theo như Bảng 2.2 giá trị để chẩn đoán một bệnh nhân bị giảm vận đồng vùng cơ tim thì giá trị EF nằm trong dải 30 - 54. Ngoài ra, trong quá trình siêu âm thực tế, ở mỗi lần đo khác nhau sẽ cho ra các giá trị khác nhau. Nguyên nhân do cùng một mặt cắt nhưng ở các lần đo khác nhau không thể bao viền giống nhau được.

Kết quả thu được cho thấy phương pháp thực nghiệm có tính ứng dụng cao và sẽ hỗ trợ rất nhiều cho bác sĩ trong việc chẩn đoán.

61

KẾT LUẬN CHUNG Kết luận:

Với ý tưởng áp dụng kỹ thuật khoa học máy tính vào trong lĩnh vực y tế bằng cách sử dụng các công cụ có sẵn để xử lí hình ảnh siêu âm tim tính toán phân suất tống máu, nghiên cứu cho ra kết quả tương đồng với kết quả thu được trên máy siêu âm. Hoàn toàn có thể sử dụng nghiên cứu để đánh giá chức năng thất trái trên các bệnh nhân cần theo dõi liên tục. Điểm nổi bật của nghiên cứu là sử dụng các công cụ miễn phí để thay thế cho các phần mềm xử lí kết quả siêu âm tim của các hãng máy siêu âm mà người sử dụng phải trả phí để sử dụng. Và đối với mỗi hãng máy khác nhau sẽ có một phần mềm xử lí kết quả riêng, không thể dùng chung với nhau được.

Định hướng phát triển tiếp theo trong tương lai:

- Thu gọn các bước thao tác lại để giúp việc sử dụng trở nên đơn giản và thuận tiện hơn.

- Đánh giá thêm nhiều chỉ số tim mạch khác để có thể thay thế hoàn được phần mềm xử lí ảnh siêu âm của hãng.

- Tạo một chương trình để xem lại và lưu trữ các dữ liệu hình ảnh siêu âm tim.

62 TÀI LIỆU THAM KHẢO [1] https://moh.gov.vn/tin-lien-quan/- /asset_publisher/vjYyM7O9aWnX/content/moi-nam-viet-nam-co-200-000- nguoi-tu-vong-do-cac-benh-tim-mach?inheritRedirect=false, truy cập lần cuối 30/11/2021 [2] https://timmachhoc.vn/khao-sat-cac-yeu-to-tien-luong-tu-vong-tren-benh- nhan-suy-tim-phan-suat-tong-mau-giam-nhap-vien-tai-vien-tim-tp-hcm/, truy cập lần cuối 30/11/2021

[3] Wayne R. Hedrick PhD, “Ultrasound Physics and Instrumentation”, 2010 [4] TS Nguyễn Anh Vũ, “Siêu âm tim từ căn bản đến nâng cao”, 2008

[5] https://www.researchgate.net/figure/The-Simpsons-method-for-

measurement-of-cardiac-output_fig3_256838286, truy cập lần cuối 30/11/2021

[6] https://gdrive.vip/google-colab-la-gi-huong-dan-su-dung-google- colab/?f...ZMmxMVAM, truy cập lần cuối 30/11/2021

[7] https://www.asecho.org/wp-

content/uploads/2017/11/ChamberQuantification_VN.pdf, truy cập lần cuối 30/11/2021

[8] https://mipav.cit.nih.gov/pubwiki/index.php/Introducing_MIPAV, truy cập lần cuối 30/11/2021

[9] https://vi.wikipedia.org/wiki/DICOM, truy cập lần cuối 30/11/2021

[10] Eric Bardinet, Laurent D.Cohen, Nicholas Ayche, “ Tracking and motion analysis of the ventricle with deformable superquadrics”, Medical Image Analysis (1996) volume 1, number 1, number 2, pp 129-149

Một phần của tài liệu Xử lí hình ảnh siêu âm tim 3d (Trang 54)