paint-brush
Bạn có thể mở dữ liệu y tế (MR, CT, X-Ray) bằng Python và tìm khối u bằng AI không?! Có lẽtừ tác giả@thebojda
3,819 lượt đọc
3,819 lượt đọc

Bạn có thể mở dữ liệu y tế (MR, CT, X-Ray) bằng Python và tìm khối u bằng AI không?! Có lẽ

từ tác giả Laszlo Fazekas8m2024/04/23
Read on Terminal Reader

dài quá đọc không nổi

Khi một lập trình viên nghe về việc xử lý dữ liệu y tế, họ có thể nghĩ đó là một điều gì đó nghiêm trọng, điều mà chỉ các trường đại học và viện nghiên cứu mới có thể xử lý được (ít nhất, đó là những gì tôi nghĩ). Như bạn có thể thấy, chúng ta đang nói về những hình ảnh thang độ xám đơn giản, rất dễ xử lý và lý tưởng cho những việc như xử lý mạng lưới thần kinh.
featured image - Bạn có thể mở dữ liệu y tế (MR, CT, X-Ray) bằng Python và tìm khối u bằng AI không?! Có lẽ
Laszlo Fazekas HackerNoon profile picture
0-item


Cách đây vài ngày tôi có đi chụp MRI. Họ nhét tôi vào một cái ống lớn, và trong 15 phút, chiếc máy xung quanh tôi kêu vo vo, vo ve và nhấp nháy. Kết thúc kỳ thi, tôi nhận được một đĩa CD chứa dữ liệu. Một nhà phát triển giỏi sẽ làm gì trong tình huống như vậy? Tất nhiên, ngay khi về đến nhà, họ bắt đầu kiểm tra dữ liệu và suy nghĩ về cách họ có thể trích xuất dữ liệu đó bằng Python .


Đĩa CD chứa một loạt các tệp DLL, một EXE và một số tệp bổ sung dành cho trình xem Windows, những tệp này rõ ràng là vô dụng trên Linux. Điểm mấu chốt nằm trong thư mục có tên DICOM. Nó chứa một loạt các tệp không có phần mở rộng trên tám thư mục. ChatGPT đã giúp tôi hiểu rằng DICOM là định dạng tiêu chuẩn thường được sử dụng để lưu trữ hình ảnh MRI, CT và X-quang. Về cơ bản, đây là một định dạng đóng gói đơn giản chứa các hình ảnh thang độ xám cùng với một số siêu dữ liệu.


Đoạn mã sau cho phép bạn dễ dàng xuất các tệp ở định dạng PNG:


 import os import pydicom from PIL import Image import sys def save_image(pixel_array, output_path): if pixel_array.dtype != 'uint8': pixel_array = ((pixel_array - pixel_array.min()) / (pixel_array.max() - pixel_array.min()) * 255).astype('uint8') img = Image.fromarray(pixel_array) img.save(output_path) print(f"Saved image to {output_path}") def process_dicom_directory(dicomdir_path, save_directory): if not os.path.exists(save_directory): os.makedirs(save_directory) dicomdir = pydicom.dcmread(dicomdir_path) for patient_record in dicomdir.patient_records: if 'PatientName' in patient_record: print(f"Patient: {patient_record.PatientName}") for study_record in patient_record.children: if 'StudyDate' in study_record: print(f"Study: {study_record.StudyDate}") for series_record in study_record.children: for image_record in series_record.children: if 'ReferencedFileID' in image_record: file_id = list(map(str, image_record.ReferencedFileID)) file_id_path = os.path.join(*file_id) print(f"Image: {file_id_path}") image_path = os.path.join(os.path.dirname(dicomdir_path), file_id_path) modified_filename = '_'.join(file_id) + '.png' image_save_path = os.path.join(save_directory, modified_filename) try: img_data = pydicom.dcmread(image_path) print("DICOM image loaded successfully.") save_image(img_data.pixel_array, image_save_path) except FileNotFoundError as e: print(f"Failed to read DICOM file at {image_path}: {e}") except Exception as e: print(f"An error occurred: {e}") if __name__ == "__main__": if len(sys.argv) < 3: print("Usage: python dicomsave.py <path_to_DICOMDIR> <path_to_save_images>") sys.exit(1) dicomdir_path = sys.argv[1] save_directory = sys.argv[2] process_dicom_directory(dicomdir_path, save_directory)


Mã mong đợi hai tham số. Đầu tiên là đường dẫn đến tệp DICOMDIR và thứ hai là thư mục nơi hình ảnh sẽ được lưu ở định dạng PNG.


Để đọc các tệp DICOM, chúng tôi sử dụng thư viện Pydicom . Cấu trúc được tải bằng hàm pydicom.dcmread , từ đó siêu dữ liệu (chẳng hạn như tên bệnh nhân) và các nghiên cứu có chứa hình ảnh có thể được trích xuất. Dữ liệu hình ảnh cũng có thể được đọc bằng dcmread . Dữ liệu thô có thể truy cập được thông qua trường pixelarray mà hàm save_image chuyển đổi thành PNG. Nó không quá phức tạp.


Kết quả là một vài hình ảnh thang độ xám hình vuông (thường là 512x512), có thể dễ dàng sử dụng sau này.


Tại sao tôi nghĩ việc viết truyện ngắn này về chuyến phiêu lưu của mình bằng định dạng DICOM là quan trọng? Khi một lập trình viên nghe về việc xử lý dữ liệu y tế, họ có thể nghĩ đó là một điều gì đó nghiêm trọng, điều mà chỉ các trường đại học và viện nghiên cứu mới có thể xử lý được (ít nhất, đó là những gì tôi nghĩ). Như bạn có thể thấy, chúng ta đang nói về những hình ảnh thang độ xám đơn giản, rất dễ xử lý và lý tưởng cho những việc như xử lý mạng lưới thần kinh.


Hãy xem xét các khả năng điều này mở ra. Đối với một cái gì đó giống như một máy phát hiện khối u đơn giản, tất cả những gì bạn cần là một mạng tích chập tương đối đơn giản và đủ số lượng mẫu. Trên thực tế, nhiệm vụ này không khác nhiều so với việc nhận dạng chó hay mèo trong hình ảnh.


Để minh họa điều này đúng như thế nào, hãy để tôi đưa ra một ví dụ. Sau khi tìm kiếm ngắn gọn trên Kaggle, tôi tìm thấy một cuốn sổ tay trong đó các khối u não được phân loại dựa trên dữ liệu MRI (hình ảnh thang độ xám). Bộ dữ liệu phân loại hình ảnh thành bốn nhóm: ba loại khối u não và loại thứ tư bao gồm hình ảnh của bộ não khỏe mạnh. Kiến trúc của mạng như sau:


 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ conv2d (Conv2D) │ (None, 164, 164, 64) │ 1,664 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d (MaxPooling2D) │ (None, 54, 54, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_1 (Conv2D) │ (None, 50, 50, 64) │ 102,464 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_1 (MaxPooling2D) │ (None, 16, 16, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_2 (Conv2D) │ (None, 13, 13, 128) │ 131,200 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_2 (MaxPooling2D) │ (None, 6, 6, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_3 (Conv2D) │ (None, 3, 3, 128) │ 262,272 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_3 (MaxPooling2D) │ (None, 1, 1, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ flatten (Flatten) │ (None, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense (Dense) │ (None, 512) │ 66,048 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_1 (Dense) │ (None, 4) │ 2,052 │ └─────────────────────────────────┴────────────────────────┴───────────────┘


Như bạn có thể thấy, một vài lớp đầu tiên bao gồm một bánh sandwich tích chập và gộp tối đa, giúp trích xuất các mẫu khối u, tiếp theo là hai lớp dày đặc thực hiện phân loại. Đây chính xác là kiến trúc được sử dụng trong mã mẫu CNN của TensorFlow sử dụng bộ dữ liệu CIFAR-10. Tôi nhớ rất rõ nó vì đây là một trong những mạng lưới thần kinh đầu tiên tôi gặp phải. Bộ dữ liệu CIFAR-10 chứa 60.000 hình ảnh được phân thành mười lớp, bao gồm hai lớp dành cho chó và mèo, như tôi đã đề cập trước đó. Kiến trúc của mạng trông như thế này:


 Model: “sequential” _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d (Conv2D) (None, 30, 30, 32) 896 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 15, 15, 32) 0 _________________________________________________________________ conv2d_1 (Conv2D) (None, 13, 13, 64) 18496 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 6, 6, 64) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 4, 4, 64) 36928 _________________________________________________________________ flatten (Flatten) (None, 1024) 0 _________________________________________________________________ dense (Dense) (None, 64) 65600 _________________________________________________________________ dense_1 (Dense) (None, 10) 650 ================================================================= Total params: 122,570 Trainable params: 122,570 Non-trainable params: 0 _________________________________________________________________


Rõ ràng là chúng ta đang xử lý cùng một kiến trúc, chỉ với các thông số hơi khác nhau, nên việc nhận biết khối u não trong ảnh MRI quả thực không khó hơn nhiều so với việc xác định mèo hay chó trong ảnh. Hơn nữa, cuốn sổ tay Kaggle nói trên còn có khả năng phân loại khối u với hiệu quả 99%!


Tôi chỉ muốn chỉ ra rằng việc trích xuất và xử lý dữ liệu y tế trong một số trường hợp có thể đơn giản như thế nào và các vấn đề có vẻ phức tạp như phát hiện khối u não thường có thể được giải quyết khá hiệu quả bằng các giải pháp đơn giản, truyền thống.


Vì vậy, tôi khuyến khích mọi người đừng né tránh dữ liệu y tế. Như bạn có thể thấy, chúng tôi đang xử lý các định dạng tương đối đơn giản và sử dụng các công nghệ tương đối đơn giản, được thiết lập tốt (mạng tích chập, bộ biến đổi tầm nhìn, v.v.), có thể đạt được tác động đáng kể ở đây. Nếu bạn tham gia một dự án chăm sóc sức khỏe nguồn mở trong thời gian rảnh rỗi chỉ như một sở thích và có thể cải thiện dù chỉ một chút cho mạng lưới thần kinh được sử dụng, thì bạn có khả năng có thể cứu được nhiều mạng sống!