{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# <center style=\"font-family: consolas; font-size: 32px; font-weight: bold;\"> MÔ HÌNH HÓA HÌNH HỌC 3D</center>\n<p><center style=\"color:#949494; font-family: consolas; font-size: 20px;\">CHUYỂN ẢNH 2D THÀNH 3D DƯỚI DẠNG ĐÁM MÂY ĐIỂM</center></p>\n\n***","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"#### <a id=\"top\"></a>\n# <div style=\"box-shadow: rgb(60, 121, 245) 0px 0px 0px 3px inset, rgb(255, 255, 255) 10px -10px 0px -3px, rgb(31, 193, 27) 10px -10px, rgb(255, 255, 255) 20px -20px 0px -3px, rgb(255, 217, 19) 20px -20px, rgb(255, 255, 255) 30px -30px 0px -3px, rgb(255, 156, 85) 30px -30px, rgb(255, 255, 255) 40px -40px 0px -3px, rgb(255, 85, 85) 40px -40px; padding:20px; margin-right: 40px; font-size:30px; font-family: consolas; text-align:center; display:fill; border-radius:15px; color:rgb(60, 121, 245);\"><b>MỤC LỤC</b></div>\n\n<div style=\"background-color: rgba(60, 121, 245, 0.03); padding:30px; font-size:15px; font-family: consolas;\">\n</div>","metadata":{}},{"cell_type":"markdown","source":"# **NÃO**","metadata":{}},{"cell_type":"markdown","source":"Import các thư viện cần thiết:","metadata":{}},{"cell_type":"code","source":"import os\nimport json\nimport glob\nimport random\nimport collections\n\nimport numpy as np\nimport pandas as pd\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nimport cv2\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\ntrain_df = pd.read_csv(\"../input/rsna-miccai-brain-tumor-radiogenomic-classification/train_labels.csv\")\ntrain_df","metadata":{"execution":{"iopub.status.busy":"2023-06-10T07:15:09.911199Z","iopub.execute_input":"2023-06-10T07:15:09.911662Z","iopub.status.idle":"2023-06-10T07:15:11.681548Z","shell.execute_reply.started":"2023-06-10T07:15:09.911621Z","shell.execute_reply":"2023-06-10T07:15:11.680402Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(5, 5))\nsns.countplot(data=train_df, x=\"MGMT_value\");","metadata":{"execution":{"iopub.status.busy":"2023-06-10T07:15:11.683713Z","iopub.execute_input":"2023-06-10T07:15:11.684655Z","iopub.status.idle":"2023-06-10T07:15:11.919369Z","shell.execute_reply.started":"2023-06-10T07:15:11.684615Z","shell.execute_reply":"2023-06-10T07:15:11.917894Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_dicom(path):\n    dicom = pydicom.read_file(path)\n    data = dicom.pixel_array\n    data = data - np.min(data)\n    if np.max(data) != 0:\n        data = data / np.max(data)\n    data = (data * 255).astype(np.uint8)\n    return data\n\n\ndef visualize_sample(\n    brats21id, \n    slice_i,\n    mgmt_value,\n    types=(\"FLAIR\", \"T1w\", \"T1wCE\", \"T2w\")\n):\n    plt.figure(figsize=(16, 5))\n    patient_path = os.path.join(\n        \"../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/\", \n        str(brats21id).zfill(5),\n    )\n    for i, t in enumerate(types, 1):\n        t_paths = sorted(\n            glob.glob(os.path.join(patient_path, t, \"*\")), \n            key=lambda x: int(x[:-4].split(\"-\")[-1]),\n        )\n        data = load_dicom(t_paths[int(len(t_paths) * slice_i)])\n        plt.subplot(1, 4, i)\n        plt.imshow(data, cmap=\"gray\")\n        plt.title(f\"{t}\", fontsize=16)\n        plt.axis(\"off\")\n\n    plt.suptitle(f\"MGMT_value: {mgmt_value}\", fontsize=16)\n    plt.show()\nfor i in random.sample(range(train_df.shape[0]), 5):\n    _brats21id = train_df.iloc[i][\"BraTS21ID\"]\n    _mgmt_value = train_df.iloc[i][\"MGMT_value\"]\n    visualize_sample(brats21id=_brats21id, mgmt_value=_mgmt_value, slice_i=0.5)","metadata":{"execution":{"iopub.status.busy":"2023-06-10T07:15:11.926535Z","iopub.execute_input":"2023-06-10T07:15:11.927014Z","iopub.status.idle":"2023-06-10T07:15:15.351882Z","shell.execute_reply.started":"2023-06-10T07:15:11.926972Z","shell.execute_reply":"2023-06-10T07:15:15.350770Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from matplotlib import animation, rc\nrc('animation', html='jshtml')\n\n\ndef create_animation(ims):\n    fig = plt.figure(figsize=(6, 6))\n    plt.axis('off')\n    im = plt.imshow(ims[0], cmap=\"gray\")\n\n    def animate_func(i):\n        im.set_array(ims[i])\n        return [im]\n\n    return animation.FuncAnimation(fig, animate_func, frames = len(ims), interval = 1000//24)\ndef load_dicom_line(path):\n    t_paths = sorted(\n        glob.glob(os.path.join(path, \"*\")), \n        key=lambda x: int(x[:-4].split(\"-\")[-1]),\n    )\n    images = []\n    for filename in t_paths:\n        data = load_dicom(filename)\n        if data.max() == 0:\n            continue\n        images.append(data)\n        \n    return images","metadata":{"execution":{"iopub.status.busy":"2023-06-10T07:15:15.353256Z","iopub.execute_input":"2023-06-10T07:15:15.354303Z","iopub.status.idle":"2023-06-10T07:15:15.369873Z","shell.execute_reply.started":"2023-06-10T07:15:15.354263Z","shell.execute_reply":"2023-06-10T07:15:15.368274Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"images = load_dicom_line(\"../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/00000/FLAIR\")\ncreate_animation(images)","metadata":{"execution":{"iopub.status.busy":"2023-06-10T07:15:15.371471Z","iopub.execute_input":"2023-06-10T07:15:15.371826Z","iopub.status.idle":"2023-06-10T07:15:45.257259Z","shell.execute_reply.started":"2023-06-10T07:15:15.371792Z","shell.execute_reply":"2023-06-10T07:15:45.255454Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"images = load_dicom_line(\"../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/00000/T2w\")\ncreate_animation(images)","metadata":{"execution":{"iopub.status.busy":"2023-06-10T07:15:45.259953Z","iopub.execute_input":"2023-06-10T07:15:45.261078Z","iopub.status.idle":"2023-06-10T07:16:16.504850Z","shell.execute_reply.started":"2023-06-10T07:15:45.261001Z","shell.execute_reply":"2023-06-10T07:16:16.502982Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":" Plotting 3D MRI scans","metadata":{}},{"cell_type":"code","source":"import tarfile\nimport plotly\nfrom plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot","metadata":{"execution":{"iopub.status.busy":"2023-06-10T14:18:18.254327Z","iopub.execute_input":"2023-06-10T14:18:18.255067Z","iopub.status.idle":"2023-06-10T14:18:18.320778Z","shell.execute_reply.started":"2023-06-10T14:18:18.254995Z","shell.execute_reply":"2023-06-10T14:18:18.318896Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Định nghĩa hàm extract_task1_files() để giải nén các tệp tin từ tệp tar:","metadata":{}},{"cell_type":"markdown","source":"Phần đầu tiên chứa một hàm và một hàm trợ giúp để giải nén tệp tin từ một tệp tin nén (tar) và lưu chúng vào một thư mục cụ thể:\n\nextract_task1_files(root=\"./data\"): Hàm này nhận đầu vào là root (đường dẫn đến thư mục đích) và thực hiện việc giải nén các tệp tin từ tệp tin nén (tar) vào thư mục root.","metadata":{}},{"cell_type":"code","source":"def extract_task1_files(root=\"./data\"):\n    tar = tarfile.open(\"../input/brats-2021-task1/BraTS2021_Training_Data.tar\")\n    tar.extractall(root)\n    tar.close()","metadata":{"execution":{"iopub.status.busy":"2023-06-10T14:18:21.549228Z","iopub.execute_input":"2023-06-10T14:18:21.549786Z","iopub.status.idle":"2023-06-10T14:18:21.556596Z","shell.execute_reply.started":"2023-06-10T14:18:21.549735Z","shell.execute_reply":"2023-06-10T14:18:21.555230Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"extract_task1_files()","metadata":{"execution":{"iopub.status.busy":"2023-06-10T14:18:25.695881Z","iopub.execute_input":"2023-06-10T14:18:25.696450Z","iopub.status.idle":"2023-06-10T14:21:10.872829Z","shell.execute_reply.started":"2023-06-10T14:18:25.696396Z","shell.execute_reply":"2023-06-10T14:21:10.870063Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Định nghĩa lớp ImageReader để đọc và xử lý hình ảnh:\n","metadata":{}},{"cell_type":"markdown","source":"Phần thứ hai chứa một lớp ImageReader và một hàm để tạo một đối tượng Scatter3d cho việc hiển thị dữ liệu ba chiều:\n\n**ImageReader**: Đây là một lớp có chức năng đọc và xử lý dữ liệu hình ảnh từ các tệp tin NIfTI. Lớp này có các thuộc tính như root (đường dẫn gốc), img_size (kích thước ảnh), normalize (có chuẩn hóa hình ảnh hay không), và single_class (sử dụng một lớp duy nhất hay không). Lớp này có các phương thức như read_file() (đọc và xử lý một tệp tin hình ảnh), và load_patient_scan() (tải dữ liệu hình ảnh cho một bệnh nhân cụ thể).\n","metadata":{}},{"cell_type":"code","source":"import nibabel as nib\nimport os\nimport albumentations as A\nimport numpy as np\n\n\nclass ImageReader:\n    def __init__(self, root:str, img_size:int=256, normalize:bool=False, single_class:bool=False):\n        pad_size = 256 if img_size > 256 else 224\n        self.resize = A.Compose(\n            [\n                A.PadIfNeeded(min_height=pad_size, min_width=pad_size, value=0),\n                A.Resize(img_size, img_size)\n            ]\n        )\n        self.normalize=normalize\n        self.single_class=single_class\n        self.root=root\n        \n    def read_file(self, path:str) -> dict:\n        scan_type = path.split('_')[-1]\n        raw_image = nib.load(path).get_fdata()\n        raw_mask = nib.load(path.replace(scan_type, 'seg.nii.gz')).get_fdata()\n        processed_frames, processed_masks = [], []\n        for frame_idx in range(raw_image.shape[2]):\n            frame = raw_image[:, :, frame_idx]\n            mask = raw_mask[:, :, frame_idx]\n            if self.normalize:\n                if frame.max() > 0:\n                    frame = frame/frame.max()\n                frame = frame.astype(np.float32)\n            else:\n                frame = frame.astype(np.uint8)\n            resized = self.resize(image=frame, mask=mask)\n            processed_frames.append(resized['image'])\n            processed_masks.append(1*(resized['mask'] > 0) if self.single_class else resized['mask'])\n        return {\n            'scan': np.stack(processed_frames, 0),\n            'segmentation': np.stack(processed_masks, 0),\n            'orig_shape': raw_image.shape\n        }\n    \n    def load_patient_scan(self, idx:int, scan_type:str='flair') -> dict:\n        patient_id = str(idx).zfill(5)\n        scan_filename = f'{self.root}/BraTS2021_{patient_id}/BraTS2021_{patient_id}_{scan_type}.nii.gz'\n        return self.read_file(scan_filename)","metadata":{"execution":{"iopub.status.busy":"2023-06-10T14:21:10.878419Z","iopub.execute_input":"2023-06-10T14:21:10.879111Z","iopub.status.idle":"2023-06-10T14:21:13.877060Z","shell.execute_reply.started":"2023-06-10T14:21:10.879039Z","shell.execute_reply":"2023-06-10T14:21:13.875646Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Định nghĩa hàm generate_3d_scatter() để tạo đối tượng Scatter3d của Plotly:","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"import plotly.graph_objects as go\nimport numpy as np\n\n \n# generate_3d_scatter(): Đây là một hàm tạo một đối tượng Scatter3d của Plotly để hiển thị \n# dữ liệu ba chiều. \n# Hàm này nhận các thông số như x, y, z (các mảng dữ liệu tọa độ), colors (mảng màu sắc), \n# size (kích thước điểm), opacity (độ mờ), scale (bảng màu), hover (thông tin hiển thị khi \n# di chuột), và name (tên của đối tượng Scatter3d).\ndef generate_3d_scatter(\n    x:np.array, y:np.array, z:np.array, colors:np.array,\n    size:int=3, opacity:float=0.2, scale:str='Teal',\n    hover:str='skip', name:str='MRI'\n) -> go.Scatter3d:\n    return go.Scatter3d(\n        x=x, y=y, z=z,\n        mode='markers', hoverinfo=hover,\n        marker = dict(\n            size=size, opacity=opacity,\n            color=colors, colorscale=scale\n        ),\n        name=name\n    )\n\n\nclass ImageViewer3d():\n#     Hàm khởi tạo (__init__) của lớp ImageViewer3d nhận các tham số như reader \n#     (đối tượng ImageReader), mri_downsample (tỉ lệ mẫu giảm của hình ảnh MRI), \n#     và mri_colorscale (bảng màu cho hình ảnh MRI).\n    def __init__(self, reader:ImageReader, mri_downsample:int=10, mri_colorscale:str='Ice', voxel_size:float=0.1) -> None:\n        self.reader = reader\n        self.mri_downsample = mri_downsample\n        self.mri_colorscale = mri_colorscale\n        self.voxel_size = voxel_size\n        \n# Phương thức load_clean_mri() được sử dụng để tải dữ liệu hình ảnh MRI sau khi đã làm sạch. \n# Phương thức này nhận đầu vào là image (mảng numpy của hình ảnh) và orig_dim (kích thước gốc \n# của hình ảnh). Nó tính toán tọa độ x, y, z của các điểm trong hình ảnh MRI có giá trị lớn hơn \n# 0, sử dụng tỉ lệ mẫu giảm mri_downsample, và trả về một từ điển chứa thông tin về các tọa độ \n# và màu sắc của các điểm.\n    def load_clean_mri(self, image:np.array, orig_dim:int) -> dict:\n        shape_offset = image.shape[1]/orig_dim\n        z, x, y = (image > 0).nonzero()\n        # only (1/mri_downsample) is sampled for the resulting image\n        x, y, z = x[::self.mri_downsample], y[::self.mri_downsample], z[::self.mri_downsample]\n        colors = image[z, x, y]\n        return dict(x=x/shape_offset, y=y/shape_offset, z=z, colors=colors)\n    \n# Phương thức load_tumor_segmentation() được sử dụng để tải dữ liệu phân-segmentation của khối u.\n# Phương thức này nhận đầu vào là image (mảng numpy của phân đoạn) và orig_dim (kích thước gốc\n# của hình ảnh). Nó tính toán tọa độ x, y, z của các điểm trong phân đoạn có giá trị tương ứng \n# với các lớp khối u, sử dụng các tỉ lệ mẫu giảm khác nhau (1/1, 1/3, 1/5) cho từng lớp, và trả \n# về một từ điển chứa thông tin về các tọa độ và màu sắc của các điểm.\n    def load_tumor_segmentation(self, image:np.array, orig_dim:int) -> dict:\n        tumors = {}\n        shape_offset = image.shape[1]/orig_dim\n        # 1/1, 1/3 and 1/5 pixels for tumor tissue classes 1(core), 2(invaded) and 4(enhancing)\n        sampling = {\n            1: 1, 2: 3, 4: 5\n        }\n        for class_idx in sampling:\n            z, x, y = (image == class_idx).nonzero()\n            x, y, z = x[::sampling[class_idx]], y[::sampling[class_idx]], z[::sampling[class_idx]]\n            tumors[class_idx] = dict(\n                x=x/shape_offset, y=y/shape_offset, z=z,\n                colors=class_idx/4\n            )\n        return tumors\n\n    \n# Phương thức collect_patient_data() được sử dụng để thu thập dữ liệu của một bệnh nhân, \n# bao gồm dữ liệu hình ảnh MRI đã làm sạch và dữ liệu phân-segmentation của khối u. \n# Phương thức này nhận đầu vào là scan (đối tượng từ phương thức load_patient_scan() của\n# lớp ImageReader). Nó sử dụng các phương thức load_clean_mri() và load_tumor_segmentation() \n# để tạo ra dữ liệu hình ảnh và phân đoạn, và trả về một danh sách các đối tượng Scatter3d \n# và tổng số điểm.\n    def collect_patient_data(self, scan:dict) -> tuple:\n        clean_mri = self.load_clean_mri(scan['scan'], scan['orig_shape'][0])\n        tumors = self.load_tumor_segmentation(scan['segmentation'], scan['orig_shape'][0])\n        \n        voxel_volume = self.voxel_size ** 3\n        markers_created = clean_mri['x'].shape[0] + sum(tumors[class_idx]['x'].shape[0] for class_idx in tumors)\n        \n        clean_mri_diem = clean_mri['x'].shape[0]\n        tumor1_diem = tumors[1]['x'].shape[0]\n        tumor2_diem = tumors[2]['x'].shape[0]\n        tumor4_diem = tumors[4]['x'].shape[0]\n        \n        clean_mri_tile = round(clean_mri_diem /markers_created*100, 2)\n        tumor1_tile = round(tumor1_diem /markers_created*100, 2)\n        tumor2_tile = round(tumor2_diem /markers_created*100, 2)\n        tumor4_tile = round(tumor4_diem /markers_created*100, 2)\n \n        clean_mri_kichthuoc = str(round(clean_mri_diem * voxel_volume, 2)) + ' cm^3'\n        tumor1_kichthuoc = str(round(tumor1_diem * voxel_volume, 2)) + ' cm^3'\n        tumor2_kichthuoc = str(round(tumor2_diem * voxel_volume, 2)) + ' cm^3'\n        tumor4_kichthuoc = str(round(tumor4_diem * voxel_volume, 2)) + ' cm^3'\n        \n        print('Brain MRI - hình ảnh MRI sạch:', clean_mri_diem ,'points,', clean_mri_tile ,'%',clean_mri_kichthuoc)\n        print('Lõi u án UT:', tumor1_diem , 'points,', tumor1_tile ,'%,', tumor1_kichthuoc)\n        print('Mô xung quanh bị xâm lấn bởi u án UT:', tumor2_diem ,'points,', tumor2_tile ,'%,', tumor2_kichthuoc)\n        print('U án UT tăng cường bằng chất có chứa Gadolinium:', tumor4_diem ,'points,', tumor4_tile,'%,', tumor4_kichthuoc)\n        \n        return [\n            generate_3d_scatter(**clean_mri, scale=self.mri_colorscale, opacity=0.3, hover='skip', name='Brain MRI - hình ảnh MRI sạch('+ clean_mri_kichthuoc +')'),\n            generate_3d_scatter(**tumors[1], opacity=0.8, hover='all', name='Lõi u án UT(' + tumor1_kichthuoc + ')'),\n            generate_3d_scatter(**tumors[2], opacity=0.4, hover='all', name='Mô xung quanh bị xâm lấn bởi u án UT(' + tumor2_kichthuoc + ')'),\n            generate_3d_scatter(**tumors[4], opacity=0.4, hover='all', name='U án UT tăng cường bằng chất có chứa Gadolinium(' + tumor4_kichthuoc + ')'),\n        ], markers_created\n \n\n# Phương thức get_3d_scan() được sử dụng để lấy dữ liệu ba chiều của một bệnh nhân và tạo đối \n# tượng biểu đồ Scatter3d tương ứng. Phương thức này nhận các đối số như patient_idx \n# (số thứ tự của bệnh nhân), và scan_type (loại quét MRI). Nó sử dụng phương thức \n# load_patient_scan() của lớp ImageReader để tải dữ liệu hình ảnh cho bệnh nhân cụ thể.\n# Sau đó, nó sử dụng phương thức collect_patient_data() để thu thập dữ liệu hình ảnh và \n# phân đoạn của bệnh nhân. Cuối cùng, nó tạo một đối tượng biểu đồ Scatter3d bằng cách sử \n# dụng các đối tượng Scatter3d từ dữ liệu thu thập và cài đặt các thuộc tính và cấu hình \n# cho biểu đồ Scatter3d và trả về đối tượng biểu đồ.\n    def get_3d_scan(self, patient_idx:int, scan_type:str='flair') -> go.Figure:\n        scan = self.reader.load_patient_scan(patient_idx, scan_type)\n        data, num_markers = self.collect_patient_data(scan)\n        fig = go.Figure(data=data)\n        fig.update_layout(\n            title=f\"[Patient id:{patient_idx}] brain MRI scan ({num_markers} points)\",\n            legend_title=\"Pixel class (click to enable/disable)\",\n            font=dict(\n                family=\"Courier New, monospace\",\n                size=14,\n            ),\n            margin=dict(\n                l=0,r=0,b=0,t=30\n            ),\n            legend=dict(itemsizing='constant')\n        )\n        return fig","metadata":{"execution":{"iopub.status.busy":"2023-06-10T15:52:28.371301Z","iopub.execute_input":"2023-06-10T15:52:28.371774Z","iopub.status.idle":"2023-06-10T15:52:28.405529Z","shell.execute_reply.started":"2023-06-10T15:52:28.371736Z","shell.execute_reply":"2023-06-10T15:52:28.404197Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"* load_tumor_segmentation(self, image:np.array, orig_dim:int) -> dict: Đây là một phương thức trong một lớp hoặc đối tượng. Nó nhận đầu vào là một mảng numpy (image) đại diện cho một hình ảnh, và một số nguyên (orig_dim) đại diện cho kích thước gốc của hình ảnh trước khi được xử lý. Phương thức này trả về một từ điển (dict) chứa thông tin về các phân đoạn u án ung thư trên hình ảnh.\n* \n* tumors = {}: Đây là một biến từ điển được khởi tạo để lưu trữ thông tin về các phân đoạn u án ung thư.\n* \n* shape_offset = image.shape[1]/orig_dim: Biến shape_offset được tính toán để xác định tỷ lệ giữa kích thước thật của hình ảnh và kích thước gốc ban đầu. Nó được sử dụng để chuyển đổi tọa độ của các điểm trong các phân đoạn ung thư.\n* \n* sampling: Đây là một từ điển chứa các thông số mẫu lấy mẫu cho các lớp mô ung thư khác nhau. Cụ thể, nó chỉ định bước mẫu lấy mẫu cho mỗi lớp, tức là số lượng điểm được lấy mẫu từ mỗi lớp tương ứng với 1 điểm thực tế.\n* \n* for class_idx in sampling:: Đây là một vòng lặp qua các phần tử trong từ điển sampling. class_idx là khóa của từng phần tử.\n* \n* (image == class_idx).nonzero(): Đây là một phép so sánh để tìm tất cả các vị trí trong image mà giá trị bằng class_idx. .nonzero() trả về các chỉ mục của các vị trí thỏa mãn điều kiện.\n* \n* x[::sampling[class_idx]], y[::sampling[class_idx]], z[::sampling[class_idx]]: Đây là một cách để lấy mẫu từ các mảng x, y, và z theo bước mẫu lấy mẫu được xác định bởi sampling[class_idx]. x[::sampling[class_idx]] chỉ lấy các phần tử từ x với bước sampling[class_idx].\n* \n* tumors[class_idx] = dict(...): Đây là một cách để thêm một phần tử vào từ điển tumors. Các thông tin về phân đoạn ung thư được lưu trữ dưới dạng một từ điển con với các khóa và giá trị tương ứng.\n* \n* clean_mri['x'].shape[0] + sum(tumors[class_idx]['x'].shape[0] for class_idx in tumors): Đây là một biểu thức tính toán tổng số lượng điểm dùng để tạo đám mây điểm 3D. Nó bao gồm số lượng điểm từ clean_mri và tổng số lượng điểm từ tất cả các lớp mô ung thư trong tumors.","metadata":{}},{"cell_type":"markdown","source":"Tạo đối tượng ImageReader và ImageViewer3d:\n","metadata":{}},{"cell_type":"markdown","source":"Tạo hai đối tượng: reader và viewer.\n\nĐối tượng reader được tạo bằng cách khởi tạo lớp ImageReader. Đối tượng này được khởi tạo với các tham số sau:\n\n* root='./data': Đường dẫn đến thư mục chứa dữ liệu hình ảnh.\n* img_size=128: Kích thước ảnh được thay đổi thành 128x128.\n* normalize=True: Chuẩn hóa ảnh.\n* single_class=False: Không áp dụng phân loại thành một lớp duy nhất.\n\nĐối tượng viewer được tạo bằng cách khởi tạo lớp ImageViewer3d. Đối tượng này được khởi tạo với các tham số sau:\n\n* reader: Đối tượng ImageReader được truyền vào để đọc dữ liệu hình ảnh.\n* mri_downsample=25: Tỉ lệ mẫu giảm của hình ảnh MRI là 25.\n* Các đối tượng reader và viewer được tạo ra để sử dụng trong việc đọc và hiển thị dữ liệu hình ảnh 3D.","metadata":{}},{"cell_type":"code","source":"reader = ImageReader('./data', img_size=128, normalize=True, single_class=False)\nviewer = ImageViewer3d(reader, mri_downsample=25)","metadata":{"execution":{"iopub.status.busy":"2023-06-10T15:52:29.705663Z","iopub.execute_input":"2023-06-10T15:52:29.706817Z","iopub.status.idle":"2023-06-10T15:52:29.714339Z","shell.execute_reply.started":"2023-06-10T15:52:29.706756Z","shell.execute_reply":"2023-06-10T15:52:29.713252Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Sử dụng viewer.get_3d_scan() để lấy hình ảnh 3D của bệnh nhân cụ thể:","metadata":{}},{"cell_type":"markdown","source":"Đoạn mã tạo ra một biểu đồ 3D của quét MRI của bệnh nhân đầu tiên (với patient_idx=0) và loại quét 't1'.\n\nĐầu tiên, viewer.get_3d_scan(0, 't1') được gọi để lấy đối tượng go.Figure biểu đồ 3D. Phương thức này gọi phương thức load_patient_scan của đối tượng reader để đọc dữ liệu quét MRI của bệnh nhân có patient_idx=0 và loại quét 't1'. Sau đó, nó sử dụng phương thức collect_patient_data của viewer để tạo dữ liệu cho biểu đồ 3D. Cuối cùng, biểu đồ 3D được tạo bằng cách sử dụng go.Figure và các dữ liệu được truyền vào.\n\nSau đó, plotly.offline.iplot(fig) được sử dụng để hiển thị biểu đồ 3D. Phương thức iplot của plotly.offline cho phép hiển thị biểu đồ trực tiếp trong notebook hoặc môi trường tương tác khác.","metadata":{}},{"cell_type":"markdown","source":"* generate_3d_scatter(**clean_mri, scale=self.mri_colorscale, opacity=0.3, hover='skip', name='Brain MRI')\n*     => Tạo đám mây điểm 3D từ clean_mri (hình ảnh MRI sạch). Điểm đám mây sẽ được tỉ lệ với màu sắc được xác định bởi mri_colorscale. Độ mờ (độ trong suốt) của đám mây là 0.3. Khi di chuột qua, sẽ bỏ qua hiển thị thông tin chi tiết. Tên đám mây điểm là \"Brain MRI\".\n\n* generate_3d_scatter(**tumors[1], opacity=0.8, hover='all', name='Necrotic tumor core')\n*     => Tạo đám mây điểm 3D từ tumors[1] (lõi u án ung thư). Điểm đám mây sẽ có độ mờ là 0.8. Khi di chuột qua, sẽ hiển thị thông tin chi tiết. Tên đám mây điểm là \"Necrotic tumor core\".\n\n* generate_3d_scatter(**tumors[2], opacity=0.4, hover='all', name='Peritumoral invaded tissue')\n*     => Tạo đám mây điểm 3D từ tumors[2] (mô xung quanh bị xâm lấn bởi u án ung thư). Điểm đám mây sẽ có độ mờ là 0.4. Khi di chuột qua, sẽ hiển thị thông tin chi tiết. Tên đám mây điểm là \"Peritumoral invaded tissue\".\n\n* generate_3d_scatter(**tumors[4], opacity=0.4, hover='all', name='GD-enhancing tumor')\n*     => Tạo đám mây điểm 3D từ tumors[4] (u án ung thư tăng cường bằng chất có chứa Gadolinium). Điểm đám mây sẽ có độ mờ là 0.4. Khi di chuột qua, sẽ hiển thị thông tin chi tiết. Tên đám mây điểm là \"GD-enhancing tumor\".\n ","metadata":{}},{"cell_type":"code","source":"fig = viewer.get_3d_scan(0, 't1')\nplotly.offline.iplot(fig)","metadata":{"execution":{"iopub.status.busy":"2023-06-10T15:52:32.792017Z","iopub.execute_input":"2023-06-10T15:52:32.792457Z","iopub.status.idle":"2023-06-10T15:52:33.158870Z","shell.execute_reply.started":"2023-06-10T15:52:32.792420Z","shell.execute_reply":"2023-06-10T15:52:33.157722Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Đoạn mã tạo ra một biểu đồ 3D của quét MRI của bệnh nhân thứ 9 (với patient_idx=9) và loại quét 'flair'.\n\nĐầu tiên, viewer.get_3d_scan(9, 'flair') được gọi để lấy đối tượng go.Figure biểu đồ 3D. Phương thức này gọi phương thức load_patient_scan của đối tượng reader để đọc dữ liệu quét MRI của bệnh nhân có patient_idx=9 và loại quét 'flair'. Sau đó, nó sử dụng phương thức collect_patient_data của viewer để tạo dữ liệu cho biểu đồ 3D. Cuối cùng, biểu đồ 3D được tạo bằng cách sử dụng go.Figure và các dữ liệu được truyền vào.\n\nSau đó, plotly.offline.iplot(fig) được sử dụng để hiển thị biểu đồ 3D. Phương thức iplot của plotly.offline cho phép hiển thị biểu đồ trực tiếp trong notebook hoặc môi trường tương tác khác.","metadata":{}},{"cell_type":"code","source":"fig = viewer.get_3d_scan(9, 'flair')\nplotly.offline.iplot(fig)","metadata":{"execution":{"iopub.status.busy":"2023-06-10T15:53:46.265277Z","iopub.execute_input":"2023-06-10T15:53:46.266057Z","iopub.status.idle":"2023-06-10T15:53:46.647522Z","shell.execute_reply.started":"2023-06-10T15:53:46.265992Z","shell.execute_reply":"2023-06-10T15:53:46.646192Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# TÁI TẠO 3D","metadata":{}},{"cell_type":"code","source":"import os\nimport glob\nfrom tqdm import tqdm\nfrom random import randint\n\nimport numpy as np\nimport pydicom\n\nimport matplotlib.pyplot as plt\nfrom matplotlib import cm\nimport matplotlib.animation as anim\nimport matplotlib.patches as mpatches\nimport matplotlib.gridspec as gridspec\n\n\nimport imageio\nfrom skimage.transform import resize\nfrom skimage.util import montage\n\nfrom IPython.display import Image as show_gif\n\nimport warnings\nwarnings.simplefilter(\"ignore\")\nsample_id = '../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/00000'\npath_x = '../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/00000/T2w/*.dcm'\nstart = len('../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/00000/T2w/Image-')\nend = len('.dcm')\npath_to_slices = sorted(glob.glob(path_x), key= lambda x: int(x[start:-end]))","metadata":{"execution":{"iopub.status.busy":"2023-06-10T15:46:57.065681Z","iopub.execute_input":"2023-06-10T15:46:57.066462Z","iopub.status.idle":"2023-06-10T15:46:57.079795Z","shell.execute_reply.started":"2023-06-10T15:46:57.066416Z","shell.execute_reply":"2023-06-10T15:46:57.078551Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ImageToGIF:\n    \"\"\"Create GIF without saving image files.\"\"\"\n    def __init__(self,\n                 size=(500, 500), \n                 xy_text=(80, 30),\n                 dpi=100, \n                 cmap='CMRmap'):\n\n        self.fig = plt.figure()\n        self.fig.set_size_inches(size[0] / dpi, size[1] / dpi)\n        self.xy_text = xy_text\n        self.cmap = cmap\n        \n        self.ax = self.fig.add_axes([0, 0, 1, 1])\n        self.ax.set_xticks([])\n        self.ax.set_yticks([])\n        self.images = []\n \n    def add(self, image, label, with_mask=False):\n        plt.set_cmap(self.cmap)\n        plt_img = self.ax.imshow(image, animated=True)\n        plt_text = self.ax.text(*self.xy_text, label, color='red')\n        to_plot = [plt_img, plt_text]\n        self.images.append(to_plot)\n        plt.close()\n \n    def save(self, filename, fps):\n        animation = anim.ArtistAnimation(self.fig, self.images)\n        animation.save(filename, writer='imagemagick', fps=fps)\n        \n\nsample_data_gif = ImageToGIF()\nlabel = sample_id.replace('/', '.').split('.')[-2]\nfilename = f'{label}_3d_2d.gif'\n\nfor i in range(len(path_to_slices)):\n    image = pydicom.read_file(path_to_slices[i]).pixel_array\n    #mask = np.clip(np.rot90(sample_mask[i]), 0, 1)\n    sample_data_gif.add(image, label=f'{label}_{str(i)}')\n\n    \nsample_data_gif.save(filename, fps=15)\nshow_gif(filename, format='png')","metadata":{"execution":{"iopub.status.busy":"2023-06-10T15:46:58.446772Z","iopub.execute_input":"2023-06-10T15:46:58.447253Z","iopub.status.idle":"2023-06-10T15:48:52.248400Z","shell.execute_reply.started":"2023-06-10T15:46:58.447192Z","shell.execute_reply":"2023-06-10T15:48:52.247288Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class Image3dToGIF3d:\n    \"\"\"\n    Displaying 3D images in 3d axes.\n    Parameters:\n        img_dim: shape of cube for resizing.\n        figsize: figure size for plotting in inches.\n    Step by step explanation - https://terbium.io/2017/12/matplotlib-3d/\n    \"\"\"\n    def __init__(self, \n                 img_dim: tuple = (55, 55, 55),\n                 figsize: tuple = (15, 10),\n                 binary: bool = False,\n                 normalizing: bool = True,\n                ):\n        \"\"\"Initialization.\"\"\"\n        self.img_dim = img_dim\n        print(img_dim)\n        self.figsize = figsize\n        self.binary = binary\n        self.normalizing = normalizing\n\n    def _explode(self, data: np.ndarray):\n        \"\"\"\n        Takes: array and return an array twice as large in each dimension,\n        with an extra space between each voxel.\n        \"\"\"\n        shape_arr = np.array(data.shape)\n        size = shape_arr[:3] * 2 - 1\n        exploded = np.zeros(np.concatenate([size, shape_arr[3:]]),\n                            dtype=data.dtype)\n        exploded[::2, ::2, ::2] = data\n        return exploded\n\n    def _expand_coordinates(self, indices: np.ndarray):\n        \"\"\" \n        Parameters:\n            indices: coordinats of array with only original values\n            (before explode transformaion)\n        \n        Returns:\n        The arrays of values each dimensions (x y z) as arguments needed \n        for the plt.figure.voxels functionto extend coordinates\n        for the rendering only colored voxels\"\"\"\n        x, y, z = indices\n        x[1::2, :, :] += 1\n        y[:, 1::2, :] += 1\n        z[:, :, 1::2] += 1\n        return x, y, z\n    \n    def _normalize(self, arr: np.ndarray):\n        \"\"\"Normilize image value between 0 and 1.\"\"\"\n        arr_min = np.min(arr)\n        return (arr - arr_min) / (np.max(arr) - arr_min)\n\n    \n    def _scale_by(self, arr: np.ndarray, factor: int = 2):\n        \"\"\"\n        Scale 3d Image to factor (guesstimated transformation).\n        Parameters:\n            arr: 3d image for scalling.\n            factor: factor for scalling.\n        \"\"\"\n        mean = np.mean(arr)\n        return (arr - mean) * factor + mean\n    \n    def get_transformed_data(self, data: np.ndarray):\n        \"\"\"Data transformation: normalization, scaling, resizing.\"\"\"\n        if self.binary:\n            resized_data = resize(data, self.img_dim, preserve_range=True)\n            return np.clip(resized_data.astype(np.uint8), 0, 1).astype(np.float32)\n            \n        norm_data = np.clip(self._normalize(data)-0.1, 0, 1) ** 0.4\n        scaled_data = np.clip(self._scale_by(norm_data) - 0.1, 0, 1)\n        resized_data = resize(scaled_data, self.img_dim, preserve_range=True)\n        \n        return resized_data\n    \n    def plot_cube(self,\n                  cube,\n                  title: str = '', \n                  init_angle: int = 0,\n                  make_gif: bool = False,\n                  path_to_save: str = 'filename.gif'\n                 ):\n        \"\"\"\n        Plot 3d data.\n        -> Take array \n        -> return an array twice as large in each dimension,\n        (with an extra space between each voxel)\n        -> expand coordinates of each voxel for core rendering (to remove gaps)\n        because additional fake voxels have been added\n        -> set each voxel’s transparency equal to its value.       \n        Parameters:\n            cube: 3d data\n            title: title for figure.\n            init_angle: angle for image plot (from 0-360).\n            make_gif: if True create gif from every 5th frames from 3d image plot.\n            path_to_save: path to save GIF file.\n            \"\"\"\n\n \n        if self.normalizing:\n            cube = self._normalize(cube)\n            \n        facecolors = cm.gist_stern(cube)          \n        facecolors[:,:,:,-1] = cube\n        facecolors = self._explode(facecolors)\n\n        filled = facecolors[:,:,:,-1] != 0\n        x, y, z = self._expand_coordinates(np.indices(np.array(filled.shape) + 1))\n\n        with plt.style.context(\"dark_background\"):\n\n            fig = plt.figure(figsize=self.figsize)\n            ax = fig.gca(projection='3d')\n\n            ax.view_init(30, init_angle)\n            ax.set_xlim(right = self.img_dim[0] * 2)\n            ax.set_ylim(top = self.img_dim[1] * 2)\n            ax.set_zlim(top = self.img_dim[2] * 2)\n            ax.set_title(title, fontsize=18, y=1.05)\n\n            ax.voxels(x, y, z, filled, facecolors=facecolors, shade=False)\n\n            if make_gif:\n                images = []\n                for angle in tqdm(range(0, 360, 5)):\n                    ax.view_init(30, angle)\n                    fname = str(angle) + '.png'\n\n                    plt.savefig(fname, dpi=120, format='png', bbox_inches='tight')\n                    images.append(imageio.imread(fname))\n                    #os.remove(fname)\n                imageio.mimsave(path_to_save, images)\n                plt.close()\n\n            else:\n                plt.show()","metadata":{"execution":{"iopub.status.busy":"2023-06-10T15:48:52.250619Z","iopub.execute_input":"2023-06-10T15:48:52.251207Z","iopub.status.idle":"2023-06-10T15:48:52.277550Z","shell.execute_reply.started":"2023-06-10T15:48:52.251168Z","shell.execute_reply":"2023-06-10T15:48:52.276008Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tensor = np.zeros((512, 512, len(path_to_slices)))\nfor i in range(len(path_to_slices)):\n    image = pydicom.read_file(path_to_slices[i]).pixel_array\n    tensor[:,:,i] = image\ntitle = sample_id.replace(\".\", \"/\").split(\"/\")[-1]\nfilename = title+\"_3d.gif\"\n\ndata_to_3dgif = Image3dToGIF3d(img_dim = (120, 120, 78))\ntransformed_data = data_to_3dgif.get_transformed_data(np.moveaxis(np.flipud(tensor), [0, 1, 2], [-1, -2, -3]))\ndata_to_3dgif.plot_cube(\n    transformed_data[:77, :100, :55],\n    title=title,\n    make_gif=True,\n    path_to_save=filename\n)\n\nshow_gif(filename, format='png')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"NUM = '00012'\n\npath_flair = f'../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/{NUM}/FLAIR/*.dcm'\nstart_flair = len(f'../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/{NUM}/FLAIR/Image-')\nend_flair = len('.dcm')\npath_to_slices_flair = sorted(glob.glob(path_flair), key= lambda x: int(x[start_flair:-end_flair]))\n\npath_t1w = f'../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/{NUM}/T1w/*.dcm'\nstart_t1w = len(f'../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/{NUM}/T1w/Image-')\nend_t1w = len('.dcm')\npath_to_slices_t1w = sorted(glob.glob(path_t1w), key= lambda x: int(x[start_t1w:-end_t1w]))\n\npath_t1wce = f'../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/{NUM}/T1wCE/*.dcm'\nstart_t1wce = len(f'../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/{NUM}/T1wCE/Image-')\nend_t1wce = len('.dcm')\npath_to_slices_t1wce = sorted(glob.glob(path_t1wce), key= lambda x: int(x[start_t1wce:-end_t1wce]))\n\npath_t2w = f'../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/{NUM}/T2w/*.dcm'\nstart_t2w = len(f'../input/rsna-miccai-brain-tumor-radiogenomic-classification/train/{NUM}/T2w/Image-')\nend_t2w = len('.dcm')\npath_to_slices_t2w = sorted(glob.glob(path_t2w), key= lambda x: int(x[start_t2w:-end_t2w]))\n\n\ndef slices_dcm_to_tensor(pth: str) -> np.ndarray:\n    tensor = np.zeros((512, 512,len(pth)))\n    for i in range(len(pth)):\n        image = pydicom.read_file(pth[i]).pixel_array\n        tensor[:,:,i] = image\n    return tensor\nflair_arr = slices_dcm_to_tensor(path_to_slices_flair)\nt1w_arr = slices_dcm_to_tensor(path_to_slices_t1w)\nt1wce_arr = slices_dcm_to_tensor(path_to_slices_t1wce)\nt2w_arr = slices_dcm_to_tensor(path_to_slices_t2w)\n\nprint(flair_arr.shape, t1w_arr.shape, t1wce_arr.shape, t2w_arr.shape)","metadata":{"execution":{"iopub.status.busy":"2023-06-10T11:56:20.286444Z","iopub.execute_input":"2023-06-10T11:56:20.287022Z","iopub.status.idle":"2023-06-10T11:56:39.455460Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig = plt.figure(figsize=(20, 10))\n\ngs = gridspec.GridSpec(nrows=2, ncols=4, height_ratios=[1, 1.5])\n\n#  Varying density along a streamline\nax0 = fig.add_subplot(gs[0, 0])\nflair = ax0.imshow(flair_arr[:,:,150], cmap='bone')\nax0.set_title(\"FLAIR\", fontsize=18, weight='bold', y=-0.2)\nfig.colorbar(flair)\n\n#  Varying density along a streamline\nax1 = fig.add_subplot(gs[0, 1])\nt1 = ax1.imshow(t1w_arr[:,:,15], cmap='bone')\nax1.set_title(\"T1\", fontsize=18, weight='bold', y=-0.2)\nfig.colorbar(t1)\n\n#  Varying density along a streamline\nax2 = fig.add_subplot(gs[0, 2])\nt1ce = ax2.imshow(t1wce_arr[:,:,160], cmap='bone')\nax2.set_title(\"T1 contrast\", fontsize=18, weight='bold', y=-0.2)\nfig.colorbar(t1ce)\n\n#  Varying density along a streamline\nax3 = fig.add_subplot(gs[0, 3])\nt2 = ax3.imshow(t2w_arr[:,:,175], cmap='bone')\nax3.set_title(\"T2\", fontsize=18, weight='bold', y=-0.2)\nfig.colorbar(t2)\n\nplt.show();","metadata":{"execution":{"iopub.status.busy":"2023-06-10T11:56:39.468101Z","iopub.execute_input":"2023-06-10T11:56:39.468574Z","iopub.status.idle":"2023-06-10T11:56:40.544887Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <div style=\"box-shadow: rgba(240, 46, 170, 0.4) -5px 5px inset, rgba(240, 46, 170, 0.3) -10px 10px inset, rgba(240, 46, 170, 0.2) -15px 15px inset, rgba(240, 46, 170, 0.1) -20px 20px inset, rgba(240, 46, 170, 0.05) -25px 25px inset; padding:20px; font-size:30px; font-family: consolas; display:fill; border-radius:15px; color: rgba(240, 46, 170, 0.7)\"> <b> ༼⁠ ⁠つ⁠ ⁠◕⁠‿⁠◕⁠ ⁠༽⁠つ Thank You!</b></div>\n\n","metadata":{}}]}