腰椎 MR 图像检测和分类(腰间盘突出)python实现

描述

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.交流与联系

QQ微信

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
腰间盘突出是一种常见的脊柱疾病,主要是指腰椎间盘的核物质向外突出或向后突出,压迫神经根或脊髓,导致腰痛、臀部疼痛、下肢疼痛、麻木等症状。康复治疗是腰间盘突出的重要治疗手段之一,下面详细介绍腰间盘突出的康复治疗方法。 1. 休息与活动:在疼痛发作期间,应该尽可能地减少活动,休息,以减轻疼痛。在疼痛缓解后,应该逐渐增加活动量,以保持肌肉的力量和柔韧性。 2. 物理治疗:物理治疗包括热敷、冷敷、按摩、牵引、电疗、超声波等,可以促进血液循环、减轻疼痛、缓解肌肉紧张等。 3. 运动治疗:适当的运动有助于加强腰部肌肉,缓解疼痛,提高身体柔韧性和稳定性。例如,可以进行下蹲、平板支撑、仰卧起坐、腹部收缩等运动。 4. 中医治疗:中医治疗包括针灸、中药疗法等,可以通过调节身体的气血、神经等,缓解疼痛、促进恢复。 5. 手术治疗:如果病情严重并且保守治疗无效,可能需要手术治疗。手术包括椎间盘摘除、椎弓根切除等。 以上是腰间盘突出康复治疗的一些常用方法,但具体治疗方案应根据个人情况而定,建议在医生指导下进行治疗。同时,需要注意的是,预防腰间盘突出也非常重要,平时要注意保持正确的坐姿、站姿、睡姿等,避免长时间保持同一姿势,以减少腰部负担。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑客0929

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值