描述
linkkeyboard_arrow_up
根据世界卫生组织的数据,腰痛是全球残疾的主要原因,2020 年影响了 6.19 亿人。大多数人在一生中的某个时刻都会经历腰痛,并且频率会随着年龄的增长而增加。疼痛和活动受限通常是脊椎病的症状,脊椎病是一组退行性脊柱疾病,包括椎间盘退化和随后的椎管狭窄(椎管狭窄)、关节下隐窝或神经孔,并伴有腰部神经的压迫或刺激。
磁共振成像 (MRI) 提供腰椎、椎间盘和神经的详细视图,使放射科医生能够评估这些疾病的存在和严重程度。对这些疾病进行正确诊断和分级有助于指导治疗和可能的手术,以帮助缓解背痛并改善患者的整体健康状况和生活质量。
RSNA 与美国神经放射学会 (ASNR) 合作举办了这项竞赛,探索人工智能是否可用于帮助使用腰椎 MR 图像检测和分类退行性脊柱疾病。
挑战将侧重于五种腰椎退行性疾病的分类:左侧神经孔狭窄、右侧神经孔狭窄、左侧关节下狭窄、右侧关节下狭窄和椎管狭窄。对于数据集中的每项影像学研究,我们提供了椎间盘水平 L1/L2、L2/L3、L3/L4、L4/L5 和 L5/S1 的五种情况中的每一种的严重程度评分(正常/轻度、中度或重度)。
为了创建地面实况数据集,RSNA 挑战规划工作组收集了来自五大洲八个站点的成像数据。这个多机构、专业策划的数据集有望改进退行性腰椎疾病的标准化分类,并支持开发工具以自动准确和快速地进行疾病分类。
椎间盘膨出和椎间盘突出的区别
在我们深入研究椎间盘膨出和椎间盘突出的细节之前,了解脊柱和椎间盘解剖学的基础知识很重要。
脊柱解剖学:您的脊柱具有提供身体支撑和结构的基本功能,让您能够站立、弯曲和扭转。脊柱还可以保护您的脊髓(中枢神经系统的一部分),脊髓是神经信号的高速公路,让您能够移动和感受感觉。脊柱由 33 块骨头组成,分为三个主要部分:
- 宫颈:形成颈部的 7 块椎骨
- 胸椎:12 块椎骨,形成上背部和中背部
- 腰椎:形成下背部的五块椎骨
每个椎骨都由纤维椎间盘 (IVD) 隔开,该椎间盘由两部分组成:
- 髓核:这是椎间盘的果冻状内核。它由大约 80% 的水以及胶原纤维组成。它的弹性使每个椎间盘都可以充当脊柱的减震器。
- 纤维环:纤维环由 7 到 15 层纤维组成,围绕和保护细胞核。每个都有一个约 30° 的轻微拱形或曲率,为椎间盘提供牵引和结构支撑。
当您的脊柱移动椎间盘时,请稍微调整以支撑椎骨。例如,当您弯腰时,前部(椎间盘前部)会压缩,后部(椎间盘后部)会伸展。
椎间盘突出和膨出的类型
椎间盘突出和膨出可能发生在脊柱的颈部、胸部或腰椎区域。
- 颈椎间盘突出症:这是当颈部椎间盘突出时。它是颈部疼痛的最常见原因之一。
- 胸椎间盘突出症:这是椎间盘突出在上背部或中背部。
- 腰椎间盘突出症:这是椎间盘突出在下背部的时候。这是下背部疼痛的常见原因。
椎间盘突出和膨出的椎间盘也可以根据它们突出的椎间盘区域(突出区)进行分类。
- 中央:当椎间盘突出进入脊髓时。
- 关节下(外侧隐窝或旁中央):当椎间盘在脊髓和孔(神经离开椎管的空间)之间挤压时。
- 椎间孔(外侧):椎间盘挤压进入椎间孔。
- 椎间孔外(远外侧):椎间盘突出超出椎间孔。
.
├── ExploreData.ipynb **This notebook
└── /kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/
├── test_images/
│ ├── 1005139/
│ │ └── 609308237/
│ │ ├── 1.dcm
│ │ └── ...
│ └── ...
├── test_series_descriptions.csv
├── train_images/
│ ├── 4003253/
│ │ └── 702807833/
│ │ ├── 1.dcm
│ │ └── ...
│ └── ...
├── train_label_coordinates.csv
├── train_series_descriptions.csv
└── train.csv
1.Loading Diagnosis Information
加载数据集中有关诊断的信息,以概述病例的分布情况。
import pandas as pd
import matplotlib.pyplot as plt
import cv2
import pydicom
import numpy as np
import os
import glob
from tqdm import tqdm
import warnings
train = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train.csv')
print("Total Cases: ", len(train))
Total Cases: 1975
train.columns
Index(['study_id', 'spinal_canal_stenosis_l1_l2', 'spinal_canal_stenosis_l2_l3', 'spinal_canal_stenosis_l3_l4', 'spinal_canal_stenosis_l4_l5', 'spinal_canal_stenosis_l5_s1', 'left_neural_foraminal_narrowing_l1_l2', 'left_neural_foraminal_narrowing_l2_l3', 'left_neural_foraminal_narrowing_l3_l4', 'left_neural_foraminal_narrowing_l4_l5', 'left_neural_foraminal_narrowing_l5_s1', 'right_neural_foraminal_narrowing_l1_l2', 'right_neural_foraminal_narrowing_l2_l3', 'right_neural_foraminal_narrowing_l3_l4', 'right_neural_foraminal_narrowing_l4_l5', 'right_neural_foraminal_narrowing_l5_s1', 'left_subarticular_stenosis_l1_l2', 'left_subarticular_stenosis_l2_l3', 'left_subarticular_stenosis_l3_l4', 'left_subarticular_stenosis_l4_l5', 'left_subarticular_stenosis_l5_s1', 'right_subarticular_stenosis_l1_l2', 'right_subarticular_stenosis_l2_l3', 'right_subarticular_stenosis_l3_l4', 'right_subarticular_stenosis_l4_l5', 'right_subarticular_stenosis_l5_s1'], dtype='object')
figure, axis = plt.subplots(1,3, figsize=(20,5))
for idx, d in enumerate(['foraminal', 'subarticular', 'canal']):
diagnosis = list(filter(lambda x: x.find(d) > -1, train.columns))
dff = train[diagnosis]
with warnings.catch_warnings():
warnings.simplefilter(action='ignore', category=FutureWarning)
value_counts = dff.apply(pd.value_counts).fillna(0).T
value_counts.plot(kind='bar', stacked=True, ax=axis[idx])
axis[idx].set_title(f'{d} distribution')
正如预期的那样,我们的许多患者在每个诊断类别中都显示出正常或轻度的评分。您还会看到一些诊断(特别是下关节狭窄类别)存在缺失数据。这是因为某些图像没有显示这些区域(特别是最上面的椎体较少进入成像范围)。
2.Loading in images
现在我们可以看到诊断的大致分布了,我们接下来将加载一个患者的示例扫描数据。我们将从获取一个患者的扫描作为示例开始。
背景信息:每位患者有一个研究(称为 StudyInstanceUID)。该研究包含多个系列(称为 SeriesInstanceUID)。在一个系列中,有多张图像,每张图像都有一个独特的 SOPInstanceUID。
所有这些数据通过之前列出的诊断类型的 CSV 文件进行关联,同时还有一个单独的 DICOM 元数据文件,其中包含有关系列描述的信息(如图像是矢状面还是轴向面,T1 还是 T2)。但系列描述的名称并不标准化。
获取每个扫描的元数据。
对于每个扫描,我们创建一个如下结构的对象:
meta_obj = {
StudyInstanceUID: {
'folder_path': ... # path to the folder,
'SeriesInstanceUIDs': [ Array of the SeriesInstanceUIDs ],
'SeriesDescriptions' [ Array of the Series Descriptions ]
}, ...
}
# List out all of the Studies we have on patients.
part_1 = os.listdir('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images')
part_1 = list(filter(lambda x: x.find('.DS') == -1, part_1))
df_meta_f = pd.read_csv('/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_series_descriptions.csv')
p1 = [(x, f"/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/{x}") for x in part_1]
meta_obj = { p[0]: { 'folder_path': p[1],
'SeriesInstanceUIDs': []
}
for p in p1 }
for m in meta_obj:
meta_obj[m]['SeriesInstanceUIDs'] = list(
filter(lambda x: x.find('.DS') == -1,
os.listdir(meta_obj[m]['folder_path'])
)
)
# grabs the correspoding series descriptions
for k in tqdm(meta_obj):
for s in meta_obj[k]['SeriesInstanceUIDs']:
if 'SeriesDescriptions' not in meta_obj[k]:
meta_obj[k]['SeriesDescriptions'] = []
try:
meta_obj[k]['SeriesDescriptions'].append(
df_meta_f[(df_meta_f['study_id'] == int(k)) &
(df_meta_f['series_id'] == int(s))]['series_description'].iloc[0])
except:
print("Failed on", s, k)
现在我们已经创建了元数据对象,让我们提取出该患者的所有图像。
patient = train.iloc[1]
ptobj = meta_obj[str(patient['study_id'])]
print(ptobj)
{'folder_path': '/kaggle/input/rsna-2024-lumbar-spine-degenerative-classification/train_images/4646740', 'SeriesInstanceUIDs': ['3666319702', '3486248476', '3201256954'], 'SeriesDescriptions': ['Sagittal T2/STIR', 'Sagittal T1', 'Axial T2']}
# Get data into the format
"""
im_list_dcm = {
'{SeriesInstanceUID}': {
'images': [
{'SOPInstanceUID': ...,
'dicom': PyDicom object
},
...,
],
'description': # SeriesDescription
},
...
}
"""
im_list_dcm = {}
for idx, i in enumerate(ptobj['SeriesInstanceUIDs']):
im_list_dcm[i] = {'images': [], 'description': ptobj['SeriesDescriptions'][idx]}
images = glob.glob(f"{ptobj['folder_path']}/{ptobj['SeriesInstanceUIDs'][idx]}/*.dcm")
for j in sorted(images, key=lambda x: int(x.split('/')[-1].replace('.dcm', ''))):
im_list_dcm[i]['images'].append({
'SOPInstanceUID': j.split('/')[-1].replace('.dcm', ''),
'dicom': pydicom.dcmread(j) })
# Function to display images
def display_images(images, title, max_images_per_row=4):
# Calculate the number of rows needed
num_images = len(images)
num_rows = (num_images + max_images_per_row - 1) // max_images_per_row # Ceiling division
# Create a subplot grid
fig, axes = plt.subplots(num_rows, max_images_per_row, figsize=(5, 1.5 * num_rows))
# Flatten axes array for easier looping if there are multiple rows
if num_rows > 1:
axes = axes.flatten()
else:
axes = [axes] # Make it iterable for consistency
# Plot each image
for idx, image in enumerate(images):
ax = axes[idx]
ax.imshow(image, cmap='gray') # Assuming grayscale for simplicity, change cmap as needed
ax.axis('off') # Hide axes
# Turn off unused subplots
for idx in range(num_images, len(axes)):
axes[idx].axis('off')
fig.suptitle(title, fontsize=16)
plt.tight_layout()
for i in im_list_dcm:
display_images([x['dicom'].pixel_array for x in im_list_dcm[i]['images']],
im_list_dcm[i]['description'])
查看病变的坐标
我们还可以显示每位患者标注的病变坐标。
def display_coor_on_img(c, i, title):
center_coordinates = (int(c['x']), int(c['y']))
radius = 10
color = (255, 0, 0) # Red color in BGR
thickness = 2
IMG = i['dicom'].pixel_array
IMG_normalized = cv2.normalize(IMG, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
IMG_with_circle = cv2.circle(IMG_normalized.copy(), center_coordinates, radius, color, thickness)
# Convert the image from BGR to RGB for correct color display in matplotlib
IMG_with_circle = cv2.cvtColor(IMG_with_circle, cv2.COLOR_BGR2RGB)
# Display the image
plt.imshow(IMG_with_circle)
plt.axis('off') # Turn off axis numbers and ticks
plt.title(title)
plt.show()
coor_entries = df_coor[df_coor['study_id'] == int(patient['study_id'])]
print("Only showing severe cases for this patient")
for idc, c in coor_entries.iterrows():
for i in im_list_dcm[str(c['series_id'])]['images']:
if int(i['SOPInstanceUID']) == int(c['instance_number']):
try:
patient_severity = patient[
f"{c['condition'].lower().replace(' ', '_')}_{c['level'].lower().replace('/', '_')}"
]
except Exception as e:
patient_severity = "unknown severity"
title = f"{i['SOPInstanceUID']} \n{c['level']}, {c['condition']}: {patient_severity} \n{c['x']}, {c['y']}"
if patient_severity == 'Severe':
display_coor_on_img(c, i, title)
仅显示该患者的严重病例
3.交流与联系
微信 | |