PaddleHub 肺炎CT影像分析

 

肺炎CT影像分析模型(Pneumonia-CT-LKM-PP)能夠高效地完成對患者CT影像的病竈檢測識別、病竈輪廓勾畫,經過必定的後處理代碼,能夠分析輸出肺部病竈的數量、體積、病竈佔比等全套定量指標。值得強調的是,該系統採用的深度學習算法模型充分訓練了所收集到的高分辨率和低分辨率的CT影像數據,能極好地適應不一樣等級CT影像設備採集的檢查數據,有望爲醫療資源受限和醫療水平偏低的基層醫院提供有效的肺炎輔助診斷工具。python

NOTE: 若是您在本地運行該項目示例,須要首先安裝PaddleHub。若是您在線運行,須要首先fork該項目示例。以後按照該示例操做便可。git

In[2]
!pip install paddlehub==1.6.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
!pip install pydicom -i https://pypi.tuna.tsinghua.edu.cn/simple
!pip install nibabel -i https://pypi.tuna.tsinghua.edu.cn/simple
!pip install scikit-image==0.15.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
 

1、定義待預測數據

以本示例中dcm_data文件夾下demo.dcm醫學影像爲例。github

關於dcm醫學影像參考:https://baike.baidu.com/item/DICOM/2171358?fr=aladdin算法

In[3]
# 讀取數據
import os
import json
import numpy as np
from mate.load_input_data import load_input_data
from mate.preprocess_lung_part import preprocess_lung_part
from lib.threshold_function_module import windowlize_image
from lib.png_rw import npy_to_png
from lib.judge_mkdir import judge_mkdir
import cv2

image_raw, info_dict = load_input_data('./dcm_data')
Begin loading data
     searching ./dcm_data
     LKM 3 contains 1 slices
     Valid imaging slices: 1
Done loading data, runtime: 0.008
In[5]
# 1. 展現醫學圖像
import matplotlib.pyplot as plt 
import matplotlib.image as mpimg 

image = windowlize_image(image_raw, 1500, -500)[0]
image = npy_to_png(image)
image = (image - float(np.min(image))) / float(np.max(image)) * 255.

image = image[np.newaxis, :, :]
image = image.transpose((1, 2, 0)).astype('float32')
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
cv2.imwrite('demo.png', image)
img = mpimg.imread('demo.png') 
plt.figure(figsize=(10,10))
plt.imshow(img) 
plt.axis('off') 
plt.show()
In[6]
# 2. 進行肺部分割須要的前處理
lung_part, info_dict = preprocess_lung_part(image_raw, info_dict)
Begin to preprocess
data.min(), data.max() -1024 250
data.min(), data.max() 0.0 0.4212963
----------     begin crop2.5D     --------
----------     end crop2.5D     --------
In[7]
# 3. 進行病竈分割須要的前處理
ww, wc = (1500, -500)
lesion_part = windowlize_image(image_raw.copy(), ww, wc)
lesion_part = np.squeeze(lesion_part, 0)
In[8]
lesion_np_path = "lesion_part.npy"
lung_np_path = "lung_part.npy"
np.save(lung_np_path, lung_part)
np.save(lesion_np_path, lesion_part)
print('肺部分割輸入:', lung_part.shape)
print('病竈分割輸入:', lesion_part.shape)
肺部分割輸入: (1, 320, 320, 3)
病竈分割輸入: (512, 512)
 

2、加載預訓練模型

PaddleHub提供了病竈分析和肺部分割的Module,即Pneumonia_CT_LKM_PP,包含病竈分割和肺部分割2個模塊,都是基於UNet進行一系列優化。json

In[9]
import paddlehub as hub

pneumonia = hub.Module(name="Pneumonia_CT_LKM_PP")
[2020-03-23 11:49:36,360] [    INFO] - Installing Pneumonia_CT_LKM_PP module
Downloading Pneumonia_CT_LKM_PP
[==================================================] 100.00%
Uncompress /home/aistudio/.paddlehub/tmp/tmpiad_bch0/Pneumonia_CT_LKM_PP
[==================================================] 100.00%
[2020-03-23 11:49:40,364] [    INFO] - Successfully installed Pneumonia_CT_LKM_PP-1.0.0
 

3、預測

PaddleHub對於支持一鍵預測的module,能夠調用module的相應預測API,完成預測功能。數組

In[10]
input_dict = {"image_np_path": [[lesion_np_path, lung_np_path]] }

results = pneumonia.segmentation(data=input_dict)
[2020-03-23 11:49:45,049] [    INFO] - 0 pretrained paramaters loaded by PaddleHub
[2020-03-23 11:49:45,055] [    INFO] - Installing Pneumonia_CT_LKM_PP_lung module
Downloading Pneumonia_CT_LKM_PP_lung
[==================================================] 100.00%
Uncompress /home/aistudio/.paddlehub/tmp/tmp3pohjqws/Pneumonia_CT_LKM_PP_lung
[==================================================] 100.00%
[2020-03-23 11:49:48,510] [    INFO] - Successfully installed Pneumonia_CT_LKM_PP_lung-1.0.0
[2020-03-23 11:49:48,723] [    INFO] - 0 pretrained paramaters loaded by PaddleHub
In[11]
# 輸出結果包含input_lesion_np_path與output_lesion_np
print(results[0])
{'input_lesion_np_path': 'lesion_part.npy', 'output_lesion_np': array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]], dtype=int64), 'output_lung_np': array([[0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0]]), 'input_lung_np_path': 'lung_part.npy'}
 

以上運行結果中:babel

input_lesion_np_path:存放用於病竈分析的numpy數組路徑工具

output_lesion_np:存放病竈分析結果post

input_lesion_np_path:存放用於肺部分割的numpy數組路徑學習

output_lung_np:存放肺部分割結果

 

4、 後處理

經過必定的後處理,將肺部分割結果映射到原圖上,再將病竈分割和肺部分割融合到一張圖上可視化。

In[12]
from PIL import Image as PILImage
from mate.postprocess_lung_part import postprocess_lung_part
from mate.merge_process import merge_process
from lib.remove_small_obj_module import remove_small_obj

# 將類別轉換爲可視化的像素點值
def get_color_map_list(num_classes):
    """ Returns the color map for visualizing the segmentation mask, which can support arbitrary number of classes. Args: num_classes: Number of classes Returns: The color map """
    color_map = num_classes * [0, 0, 0]
    for i in range(0, num_classes):
        j = 0
        lab = i
        while lab:
            color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j))
            color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j))
            color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j))
            j += 1
            lab >>= 3

    return color_map
    
color_map_lesion = get_color_map_list(num_classes=2)
color_map_lung = get_color_map_list(num_classes=3)

lung_part = postprocess_lung_part(results[0]['output_lung_np'], info_dict)
        
lesion_part = results[0]['output_lesion_np'].astype(np.uint8)
for i in range(len(lesion_part)):
    lesion_part[i] = remove_small_obj(lesion_part[i], 10)
    
# 對肺部分割結果和病竈分割結果進行後處理
lung_part, lesion_part = merge_process(image_raw, lung_part, lesion_part)
process lung part post process:   0%|          | 0/1 [00:00<?, ?it/s]/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/skimage/morphology/misc.py:211: UserWarning: the min_size argument is deprecated and will be removed in 0.16. Use area_threshold instead.
  warn("the min_size argument is deprecated and will be removed in " +
process lung part post process: 100%|██████████| 1/1 [00:00<00:00, 68.80it/s]
Begin to postprocess
Done to postprocess
In[13]
# 展現肺部分割的圖片,黃色表示右肺,綠色表示左肺
pred_mask = PILImage.fromarray(np.argmax(lung_part, -1)[0].astype(np.uint8), mode='P')
pred_mask.putpalette(color_map_lung)
pred_mask = pred_mask.convert('RGB')

lung_merge_img = np.where(pred_mask, pred_mask, img)
fig, axarr = plt.subplots(1, 1, figsize=(10, 10))

axarr.axis('off')
axarr.imshow(lung_merge_img)
2020-03-23 11:50:09,206-WARNING: Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
<matplotlib.image.AxesImage at 0x7f73a0086210>
In[14]
# 展現病竈分割的圖片,能夠看到左下角非肺部部分存在誤檢。
# 後續咱們與肺部分割相結合,去除誤檢
resmap = results[0]['output_lesion_np']
pred_mask = PILImage.fromarray(resmap.astype(np.uint8), mode='P')
pred_mask.putpalette(color_map_lesion)

pred_mask = pred_mask.convert('RGB')

lesion_merge_img = np.where(pred_mask, pred_mask, img)

fig, axarr = plt.subplots(1, 1, figsize=(10, 10))

axarr.axis('off')
axarr.imshow(lesion_merge_img)
2020-03-23 11:50:12,670-WARNING: Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
<matplotlib.image.AxesImage at 0x7f73a0063a90>
In[15]
#將兩個結果合併,排除非肺部的病竈分割
import json
import numpy as np
from lib.info_dict_module import InfoDict
from mate.save_merged_png_cv2 import save_merged_png_cv2
from lib.judge_mkdir import judge_mkdir
In[16]
# 融合肺部分割結果和病竈分割結果

image = windowlize_image(image_raw, 1500, -500)[0]
image = npy_to_png(image)
image = (image - float(np.min(image))) / float(np.max(image)) * 255.

lung = lung_part[0,..., 1] + lung_part[0,..., 2]
binary = lung * 255
binary = binary.astype(np.uint8)
try:
    _, lung_contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
except:
    lung_contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

binary = lesion_part[0] * 255
binary = binary.astype(np.uint8)

try:
    _, lesion_contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
except:
    lesion_contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

image = image[np.newaxis, :, :]
image = image.transpose((1, 2, 0)).astype('float32')
image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)

cv2.drawContours(image, lesion_contours, -1, (0, 0, 255), 2)
cv2.drawContours(image, lung_contours, -1, (0, 255, 0), 2)

cv2.imwrite('merged.png', image)
True
In[17]
# 能夠看到此時誤檢的非肺部分已經被去除
img = mpimg.imread('merged.png') 
plt.figure(figsize=(10,10))
plt.imshow(img) 
plt.axis('off') 
plt.show()
In[18]
# 最後咱們根據預測計算一下病竈佔比,病竈體積,病竈個數
from lib.c1_cal_lesion_percent import cal_lesion_percent
from lib.c2_cal_lesion_volume import cal_lesion_volume
from lib.c3_cal_lesion_num import cal_lesion_num
from lib.c4_cal_histogram import cal_histogram
from lib.c5_normal_statistics import normal_statistics

def cal_metrics(image_raw, lung_part, lesion_part, spacing_list):
    """ 進行指標計算 總體流程: 1. 分別獲得左右肺和左右病竈 2. 計算病竈佔比 3. 計算病竈體積 4. 計算病竈個數 5. 計算直方圖 """
    print('cal the statistics metrics')
    # 1. 分別獲得左右肺和左右病竈
    lung_l = lung_part[..., 1]
    lung_r = lung_part[..., 2]
    lesion_l = lesion_part.copy() * lung_l
    lesion_r = lesion_part.copy() * lung_r

    lung_tuple = (lung_l, lung_r, lung_part)
    lesion_tuple = (lesion_l, lesion_r, lesion_part)

    # 2. 計算病竈佔比
    lesion_percent_dict = cal_lesion_percent(lung_tuple, lesion_tuple)

    # 3. 計算病竈體積
    lesion_volume_dict = cal_lesion_volume(lesion_tuple, spacing_list)

    # 4. 計算病竈個數
    lesion_num_dict = cal_lesion_num(lesion_tuple)

    # 5. 計算直方圖
    hu_statistics_dict = cal_histogram(image_raw, lung_tuple)

    metrics_dict = {
        'lesion_num': lesion_num_dict,
        'lesion_volume': lesion_volume_dict,
        'lesion_percent': lesion_percent_dict,
        'hu_statistics': hu_statistics_dict,
        'normal_statistics': normal_statistics
    }

    return metrics_dict
    
# 進行指標計算
metrics_dict = cal_metrics(image_raw, lung_part, lesion_part, info_dict.spacing_list)
cal the statistics metrics
In[19]
# 打印一下各項指標, 'lung_l'爲左肺,'lung_r'爲右肺, 'lung_all'爲兩個肺。
print('病竈個數', metrics_dict['lesion_num'])
print('病竈體積', metrics_dict['lesion_volume'])
print('病竈佔比', metrics_dict['lesion_percent'])
病竈個數 {'lung_l': 0, 'lung_r': 0, 'lung_all': 0}
病竈體積 {'lung_l': 0.0, 'lung_r': 0.0, 'lung_all': 0.0}
病竈佔比 {'lung_l': 0.0, 'lung_r': 0.0, 'lung_all': 0.0}
 

如您在運行本教程有任何疑問,能夠經過如下兩種方式提問:

  • 飛槳官方技術交流QQ羣:703252161
  • PaddleHub issues https://github.com/PaddlePaddle/PaddleHub/issues

使用AI Studio一鍵上手實踐項目吧:https://aistudio.baidu.com/aistudio/projectdetail/312508

相關文章
相關標籤/搜索