lingchmao's picture
create app.py
52a9229 verified
raw
history blame
12.2 kB
import os
import numpy as np
import gradio as gr
import torch
import monai
import morphsnakes as ms
from utils.sliding_window import sw_inference
from utils.tumor_features import generate_features
from monai.networks.nets import SegResNetVAE
from monai.transforms import (
LoadImage, Orientation, Compose, ToTensor, Activations,
FillHoles, KeepLargestConnectedComponent, AsDiscrete, ScaleIntensityRange
)
# global params
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
examples_path = [
os.path.join(THIS_DIR, 'examples', 'HCC_003.nrrd'),
os.path.join(THIS_DIR, 'examples', 'HCC_006.nrrd'),
os.path.join(THIS_DIR, 'examples', 'HCC_007.nrrd'),
os.path.join(THIS_DIR, 'examples', 'HCC_018.nrrd')
]
models_path = {
"liver": os.path.join(THIS_DIR, 'checkpoints', 'liver_3DSegResNetVAE.pth'),
"tumor": os.path.join(THIS_DIR, 'checkpoints', 'tumor_3DSegResNetVAE_weak_morp.pth')
}
cache_path = {
"liver mask": "liver_mask.npy",
"tumor mask": "tumor_mask.npy"
}
device = "cpu"
mydict = {}
def render(image_name, x, selected_slice):
if not isinstance(image_name, str) or '/' in image_name:
image_name = image_name.name.split('/')[-1].replace(".nrrd","")
if 'img' not in mydict[image_name].keys():
return (np.zeros((512, 512)), []), f'z-value: {x}, (zmin: {None}, zmax: {None})'
# set slider ranges
zmin, zmax = 0, mydict[image_name]['img'].shape[-1] - 1
if x > zmax: x = zmax
if x < zmin: x = zmin
# image
img = mydict[image_name]['img'][:,:,x]
img = (img - np.min(img)) / (np.max(img) - np.min(img)) # scale to 0 and 1
# masks
annotations = []
if 'liver mask' in mydict[image_name].keys():
annotations.append((mydict[image_name]['liver mask'][:,:,x], "segmented liver"))
if 'tumor mask' in mydict[image_name].keys():
annotations.append((mydict[image_name]['tumor mask'][:,:,x], "segmented tumor"))
return img, annotations
def load_liver_model():
liver_model = SegResNetVAE(
input_image_size=(512,512,16),
vae_estimate_std=False,
vae_default_std=0.3,
vae_nz=256,
spatial_dims=3,
blocks_down=[1, 2, 2, 4],
blocks_up=[1, 1, 1],
init_filters=16,
in_channels=1,
norm='instance',
out_channels=2,
dropout_prob=0.2,
)
liver_model.load_state_dict(torch.load(models_path['liver'], map_location=torch.device(device)))
return liver_model
def load_tumor_model():
tumor_model = SegResNetVAE(
input_image_size=(256,256,32),
vae_estimate_std=False,
vae_default_std=0.3,
vae_nz=256,
spatial_dims=3,
blocks_down=[1, 2, 2, 4],
blocks_up=[1, 1, 1],
init_filters=16,
in_channels=1,
norm='instance',
out_channels=3,
dropout_prob=0.2,
)
tumor_model.load_state_dict(torch.load(models_path['tumor'], map_location=torch.device('cpu')))
return tumor_model
def load_image(image, slider, selected_slice):
global mydict
image_name = image.name.split('/')[-1].replace(".nrrd","")
mydict = {image_name: {}}
preprocessing_liver = Compose([
# load image
LoadImage(reader="NrrdReader", ensure_channel_first=True),
# ensure orientation
Orientation(axcodes="PLI"),
# convert to tensor
ToTensor()
])
input = preprocessing_liver(image.name)
mydict[image_name]["img"] = input[0].numpy() # first channel
print("Loaded image", image_name)
image, annotations = render(image_name, slider, selected_slice)
return f"Your image is successfully loaded! Please use the slider to view the image (zmin: 1, zmax: {input.shape[-1]}).", (image, annotations)
def segment_tumor(image_name):
if os.path.isfile(f"cache/{image_name}_{cache_path['tumor mask']}"):
mydict[image_name]['tumor mask'] = np.load(f"cache/{image_name}_{cache_path['tumor mask']}")
if 'tumor mask' in mydict[image_name].keys() and mydict[image_name]['tumor mask'] is not None:
return
input = torch.from_numpy(mydict[image_name]['img'])
tumor_model = load_tumor_model()
preprocessing_tumor = Compose([
ScaleIntensityRange(a_min=-200, a_max=250, b_min=0.0, b_max=1.0, clip=True)
])
postprocessing_tumor = Compose([
Activations(sigmoid=True),
# Convert to binary predictions
AsDiscrete(argmax=True, to_onehot=3),
# Remove small connected components for 1=liver and 2=tumor
KeepLargestConnectedComponent(applied_labels=[2]),
# Fill holes in the binary mask for 1=liver and 2=tumor
FillHoles(applied_labels=[2]),
ToTensor()
])
# Preprocessing
input = preprocessing_tumor(input)
input = torch.multiply(input, torch.from_numpy(mydict[image_name]['liver mask'])) # mask non-liver regions
# Generate segmentation
with torch.no_grad():
segmented_mask = sw_inference(tumor_model, input[None, None, :], (256,256,32), False, discard_second_output=True, overlap=0.2)[0] # input dimensions [B,C,H,W,Z]
# Postprocess image
segmented_mask = postprocessing_tumor(segmented_mask)[-1].numpy() # background, liver, tumor
segmented_mask = ms.morphological_chan_vese(segmented_mask, iterations=2, init_level_set=segmented_mask)
segmented_mask = np.multiply(segmented_mask, mydict[image_name]['liver mask']) # Mask regions outside liver
mydict[image_name]["tumor mask"] = segmented_mask
# Saving
np.save(f"cache/{image_name}_{cache_path['tumor mask']}", mydict[image_name]["tumor mask"])
print(f"tumor mask saved to 'cache/{image_name}_{cache_path['tumor mask']}")
return
def segment_liver(image_name):
if os.path.isfile(f"cache/{image_name}_{cache_path['liver mask']}"):
mydict[image_name]['liver mask'] = np.load(f"cache/{image_name}_{cache_path['liver mask']}")
if 'liver mask' in mydict[image_name].keys() and mydict[image_name]['liver mask'] is not None:
return
input = torch.from_numpy(mydict[image_name]['img'])
# load model
liver_model = load_liver_model()
# HU Windowing
preprocessing_liver = Compose([
ScaleIntensityRange(a_min=-150, a_max=250, b_min=0.0, b_max=1.0, clip=True)
])
postprocessing_liver = Compose([
# Apply softmax activation to convert logits to probabilities
Activations(sigmoid=True),
# Convert predicted probabilities to discrete values (0 or 1)
AsDiscrete(argmax=True, to_onehot=None),
# Remove small connected components for 1=liver and 2=tumor
KeepLargestConnectedComponent(applied_labels=[1]),
# Fill holes in the binary mask for 1=liver and 2=tumor
FillHoles(applied_labels=[1]),
ToTensor()
])
# Preprocessing
input = preprocessing_liver(input)
# Generate segmentation
with torch.no_grad():
segmented_mask = sw_inference(liver_model, input[None, None, :], (512,512,16), False, discard_second_output=True, overlap=0.2)[0] # input dimensions [B,C,H,W,Z]
# Postprocess image
segmented_mask = postprocessing_liver(segmented_mask)[0].numpy() # first channel
mydict[image_name]["liver mask"] = segmented_mask
print(f"liver mask shape: {segmented_mask.shape}")
# Saving
np.save(f"cache/{image_name}_{cache_path['liver mask']}", mydict[image_name]["liver mask"])
print(f"liver mask saved to cache/{image_name}_{cache_path['liver mask']}")
return
def segment(image, selected_mask, slider, selected_slice):
image_name = image.name.split('/')[-1].replace(".nrrd", "")
download_liver = gr.DownloadButton(label="Download liver mask", visible = False)
download_tumor = gr.DownloadButton(label="Download tumor mask", visible = False)
if 'liver mask' in selected_mask:
print('Segmenting liver...')
segment_liver(image_name)
download_liver = gr.DownloadButton(label="Download liver mask", value=f"cache/{image_name}_{cache_path['liver mask']}", visible=True)
if 'tumor mask' in selected_mask:
print('Segmenting tumor...')
segment_tumor(image_name)
download_tumor = gr.DownloadButton(label="Download tumor mask", value=f"cache/{image_name}_{cache_path['tumor mask']}", visible=True)
image, annotations = render(image, slider, selected_slice)
return f"Segmentation is completed! ", download_liver, download_tumor, (image, annotations)
def generate_summary(image):
image_name = image.name.split('/')[-1].replace(".nrrd","")
features = generate_features(mydict[image_name]["img"], mydict[image_name]["liver mask"], mydict[image_name]["tumor mask"])
print(features)
return ""
with gr.Blocks() as app:
with gr.Column():
gr.Markdown(
"""
# Lung Tumor Segmentation App
This tool is designed to assist in the identification and segmentation of lung and tumor from medical images. By uploading a CT scan image, a pre-trained machine learning model will automatically segment the lung and tumor regions. Segmented tumor's characteristics such as shape, size, and location are then analyzed to produce an AI-generated diagnosis report of the lung cancer.
⚠️ Important disclaimer: these model outputs should NOT replace the medical diagnosis of healthcare professionals. For your reference, our model was trained on the [HCC-TACE-Seg dataset](https://www.cancerimagingarchive.net/collection/hcc-tace-seg/) and achieved 0.954 dice score for lung segmentation and 0.570 dice score for tumor segmentation. Improving tumor segmentation is still an active area of research!
""")
with gr.Row():
comment = gr.Textbox(label='Your tool guide:', value="👋 Hi there, welcome to explore the power of AI for automated medical image analysis with our user-friendly app! Start by uploading a CT scan image. Note that for now we accept .nrrd formats only.")
with gr.Row():
with gr.Column(scale=2):
image_file = gr.File(label="Step 1: Upload a CT image (.nrrd)", file_count='single', file_types=['.nrrd'], type='filepath')
btn_upload = gr.Button("Upload")
with gr.Column(scale=2):
selected_mask = gr.CheckboxGroup(label='Step 2: Select mask to produce', choices=['liver mask', 'tumor mask'], value = ['liver mask'])
btn_segment = gr.Button("Segment")
with gr.Row():
slider = gr.Slider(1, 100, step=1, label="Slice (z)")
selected_slice = gr.State(value=1)
with gr.Row():
myimage = gr.AnnotatedImage(label="Image Viewer", height=1000, width=1000, color_map={"segmented liver": "#0373fc", "segmented tumor": "#eb5334"})
with gr.Row():
with gr.Column(scale=2):
btn_download_liver = gr.DownloadButton("Download liver mask", visible=False)
with gr.Column(scale=2):
btn_download_tumor = gr.DownloadButton("Download tumor mask", visible=False)
with gr.Row():
report = gr.Textbox(label='Step 4. Generate summary report using AI:')
with gr.Row():
btn_report = gr.Button("Generate summary")
gr.Examples(
examples_path,
[image_file],
)
btn_upload.click(fn=load_image,
inputs=[image_file, slider, selected_slice],
outputs=[comment, myimage],
)
btn_segment.click(fn=segment,
inputs=[image_file, selected_mask, slider, selected_slice],
outputs=[comment, btn_download_liver, btn_download_tumor, myimage],
)
slider.change(
render,
inputs=[image_file, slider, selected_slice],
outputs=[myimage]
)
btn_report.click(fn=generate_summary,
outputs=report
)
app.launch()