COVID-19 Detection with ML and CXR | تشخیص کرونا توسط یادگیری ماشین و CXR
In short: when Iran ran short of COVID-19 test kits, I built a demo that looks at a chest X-ray and tells a normal lung from an abnormal one. Full Keras code below. زمانیکه شروع به ساخت این برنامه…
- published
- read time
- 6 min
- words
- 1,261
- lang
- en

In short: when Iran ran short of COVID-19 test kits, I built a demo that looks at a chest X-ray and tells a normal lung from an abnormal one. Full Keras code below.
زمانیکه شروع به ساخت این برنامه کردم بحران تعداد کیت داشتیم. با توجه به علائم کیفی مانند تب و سرفه و غیره نمیتوان بطور قطع گفت که فرد مبتلا به این بیماریست یا خیر.
بنابر مقاله های جدید منتشر شده در دو هفته اخیر متوجه شدم که اسکن ریه میتونه خیلی واضح توی روند تشخیص کمک کنه. با مطالعه مقالات و پیدا کردن دیتابیسشون تونستم یه شمای کلی از مساله پیدا کنم. متاسفانه تنها مشکل این نرم افزار کم بودن داده های آموزش هست که خوب میشه اگر بیمارستان ها بتونند داده هاشون رو به اشتراک بزارن.
علاوه بر عکس های ایکس ری، نیاز به داده های بیشتر مثل سچوریشن اکسیژن خون، میزان دمای بدن، جنسیت و فاکتور های دیگر رو میشه بعنوان ورودی به سیستم داد تا تخمین دقیق تری داشته باشیم.
When I started on this, we had a shortage of detection kits for the patients showing symptoms. Symptoms alone, fever and cough and the rest, do not tell you whether someone has it. After reading the papers coming out at the time, I built a demo app to distinguish normal lungs from abnormal ones.
X-rays are not the only signal worth feeding in. Blood oxygen saturation, body temperature, sex and other factors would all sharpen the estimate.

The code
import os
import numpy as np
import pandas as pd
import random
import cv2
import matplotlib.pyplot as plt
import keras.backend as K
from keras.models import Model, Sequential
from keras.layers import Input, Dense, Flatten, Dropout, BatchNormalization
from keras.layers import Conv2D, SeparableConv2D, MaxPool2D, LeakyReLU, Activation
from keras.optimizers import Adam
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
import tensorflow as tf
seed = 200
np.random.seed(seed)
tf.random.set_seed(seed)
input_path = 'chest_xray/DATA/'
fig, img = plt.subplots(2, 3, figsize=(15, 15))
img = img.ravel()
plt.tight_layout()
for i, _set in enumerate(['train', 'val', 'test']):
set_path = input_path+_set
img[i].imshow(plt.imread(set_path+'/NORMAL/'+os.listdir(set_path+'/NORMAL')[0]), cmap='gray')
img[i].set_title('Normal'.format(_set))
img[i+3].imshow(plt.imread(set_path+'/COVID/'+os.listdir(set_path+'/COVID')[0]), cmap='gray')
img[i+3].set_title('nCoVid-19'.format(_set))
for _set in ['train', 'val', 'test']:
n_normal = len(os.listdir(input_path + _set + '/NORMAL'))
n_infect = len(os.listdir(input_path + _set + '/COVID'))
print('normal images: {}, nCoVid-19 images: {}'.format(_set, n_normal, n_infect))
def process_data(img_dims, batch_size):
train_datagen = ImageDataGenerator(rescale=1./255, zoom_range=0.3, vertical_flip=True)
test_val_datagen = ImageDataGenerator(rescale=1./255)
train_gen = train_datagen.flow_from_directory(
directory=input_path+'train',
target_size=(img_dims, img_dims),
batch_size=batch_size,
class_mode='binary',
shuffle=True)
test_gen = test_val_datagen.flow_from_directory(
directory=input_path+'test',
target_size=(img_dims, img_dims),
batch_size=batch_size,
class_mode='binary',
shuffle=True)
test_data = []
test_labels = []
for cond in ['/NORMAL/', '/COVID/']:
for img in (os.listdir(input_path + 'test' + cond)):
img = plt.imread(input_path+'test'+cond+img)
img = cv2.resize(img, (img_dims, img_dims))
img = np.dstack([img, img, img])
img = img.astype('float32') / 255
if cond=='/NORMAL/':
label = 0
elif cond=='/COVID/':
label = 1
test_data.append(img)
test_labels.append(label)
test_data = np.array(test_data)
test_labels = np.array(test_labels)
return train_gen, test_gen, test_data, test_labels
img_dims = 150
epochs = 10
batch_size = 32
train_gen, test_gen, test_data, test_labels = process_data(img_dims, batch_size)
inputs = Input(shape=(img_dims, img_dims, 3))
x = Conv2D(filters=16, kernel_size=(3, 3), activation='relu', padding='same')(inputs)
x = Conv2D(filters=16, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = SeparableConv2D(filters=32, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=32, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = SeparableConv2D(filters=64, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=64, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = SeparableConv2D(filters=128, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=128, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = Dropout(rate=0.2)(x)
x = SeparableConv2D(filters=256, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=256, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = Dropout(rate=0.2)(x)
x = SeparableConv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = Dropout(rate=0.2)(x)
output = Dense(units=1, activation='sigmoid')(x)
model = Model(inputs=inputs, outputs=output)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
checkpoint = ModelCheckpoint(filepath='best_weights.hdf5', save_best_only=True, save_weights_only=True)
lr_reduce = ReduceLROnPlateau(monitor='val_loss', factor=0.3, patience=2, verbose=2, mode='max')
early_stop = EarlyStopping(monitor='val_loss', min_delta=0.1, patience=1, mode='min')
hist = model.fit_generator(train_gen, steps_per_epoch=train_gen.samples // batch_size,epochs=epochs, validation_data=test_gen,validation_steps=test_gen.samples // batch_size,callbacks=[checkpoint, lr_reduce])
Where the images came from
- pubs.rsna.org/2019-nCoV
- itnonline.com: CT provides best diagnosis for novel coronavirus
- r/COVID19: chest CT images of COVID-19 lung involvement
- radiopaedia.org: COVID-19 pneumonia case
Update 1: someone built a better version
A group in China took the same idea further, using CT images instead:
A deep learning algorithm using CT images to screen for Corona Virus Disease (COVID-19). Shuai Wang, Bo Kang, Jinlu Ma, Xianjun Zeng, Mingming Xiao, Jia Guo, Mengjiao Cai, Jingyi Yang, Yaodong Li, Xiangfei Meng, Bo Xu. doi: 10.1101/2020.02.14.20023028
Update 2: the dataset
The dataset archive that used to be attached to this post is no longer hosted here. You can rebuild it from the image sources listed above.
Update 3: fixing the errors people hit
If the code above throws errors for you, use this version instead. It adds the flatten and dense layers before the output.
import os
import numpy as np
import pandas as pd
import random
import cv2
import matplotlib.pyplot as plt
import keras.backend as K
from keras.models import Model, Sequential
from keras.layers import Input, Dense, Flatten, Dropout, BatchNormalization
from keras.layers import Conv2D, SeparableConv2D, MaxPool2D, LeakyReLU, Activation
from keras.optimizers import Adam
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping
import tensorflow as tf
seed = 200
np.random.seed(seed)
tf.random.set_seed(seed)
input_path = 'chest_xray/DATA/'
fig, img = plt.subplots(2, 3, figsize=(15, 15))
img = img.ravel()
plt.tight_layout()
for i, _set in enumerate(['train', 'val', 'test']):
set_path = input_path+_set
img[i].imshow(plt.imread(set_path+'/NORMAL/'+os.listdir(set_path+'/NORMAL')[0]), cmap='gray')
img[i].set_title('Normal'.format(_set))
img[i+3].imshow(plt.imread(set_path+'/COVID/'+os.listdir(set_path+'/COVID')[0]), cmap='gray')
img[i+3].set_title('nCoVid-19'.format(_set))
for _set in ['train', 'val', 'test']:
n_normal = len(os.listdir(input_path + _set + '/NORMAL'))
n_infect = len(os.listdir(input_path + _set + '/COVID'))
print('normal images: {}, nCoVid-19 images: {}'.format(_set, n_normal, n_infect))
def process_data(img_dims, batch_size):
train_datagen = ImageDataGenerator(rescale=1./255, zoom_range=0.3, vertical_flip=True)
test_val_datagen = ImageDataGenerator(rescale=1./255)
train_gen = train_datagen.flow_from_directory(
directory=input_path+'train',
target_size=(img_dims, img_dims),
batch_size=batch_size,
class_mode='binary',
shuffle=True)
test_gen = test_val_datagen.flow_from_directory(
directory=input_path+'test',
target_size=(img_dims, img_dims),
batch_size=batch_size,
class_mode='binary',
shuffle=True)
test_data = []
test_labels = []
for cond in ['/NORMAL/', '/COVID/']:
for img in (os.listdir(input_path + 'test' + cond)):
img = plt.imread(input_path+'test'+cond+img)
img = cv2.resize(img, (img_dims, img_dims))
img = np.dstack([img, img, img])
img = img.astype('float32') / 255
if cond=='/NORMAL/':
label = 0
elif cond=='/COVID/':
label = 1
test_data.append(img)
test_labels.append(label)
test_data = np.array(test_data)
test_labels = np.array(test_labels)
return train_gen, test_gen, test_data, test_labels
img_dims = 150
epochs = 10
batch_size = 32
train_gen, test_gen, test_data, test_labels = process_data(img_dims, batch_size)
inputs = Input(shape=(img_dims, img_dims, 3))
x = Conv2D(filters=16, kernel_size=(3, 3), activation='relu', padding='same')(inputs)
x = Conv2D(filters=16, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = SeparableConv2D(filters=32, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=32, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = SeparableConv2D(filters=64, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=64, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = SeparableConv2D(filters=128, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=128, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = Dropout(rate=0.2)(x)
x = SeparableConv2D(filters=256, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=256, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = Dropout(rate=0.2)(x)
x = SeparableConv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = SeparableConv2D(filters=512, kernel_size=(3, 3), activation='relu', padding='same')(x)
x = BatchNormalization()(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = Dropout(rate=0.2)(x)
x = Flatten()(x)
x = Dense(units=512, activation='relu')(x)
x = Dropout(rate=0.7)(x)
x = Dense(units=128, activation='relu')(x)
x = Dropout(rate=0.5)(x)
x = Dense(units=64, activation='relu')(x)
x = Dropout(rate=0.3)(x)
x = Dense(units=32, activation='relu')(x)
x = Dropout(rate=0.1)(x)
output = Dense(units=1, activation='sigmoid')(x)
model = Model(inputs=inputs, outputs=output)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
checkpoint = ModelCheckpoint(filepath='best_weights.hdf5', save_best_only=True, save_weights_only=True)
lr_reduce = ReduceLROnPlateau(monitor='val_loss', factor=0.3, patience=2, verbose=2, mode='max')
early_stop = EarlyStopping(monitor='val_loss', min_delta=0.1, patience=1, mode='min')
hist = model.fit_generator(train_gen, steps_per_epoch=train_gen.samples // batch_size,epochs=epochs, validation_data=test_gen,validation_steps=test_gen.samples // batch_size,callbacks=[checkpoint, lr_reduce])
related