test
commited on
Adding model code
Browse files- .gitattributes +1 -0
- README +1 -0
- augment_images.py +64 -0
- data_check.py +55 -0
- evaluate_model.py +21 -0
- model.py +20 -0
- poetry.lock +0 -0
- pyproject.toml +25 -0
- train_model.py +56 -0
- xray_image_classifier_model.keras +3 -0
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
xray_image_classifier_model.keras filter=lfs diff=lfs merge=lfs -text
|
README
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
poetry run python train_model.py
|
augment_images.py
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from PIL import Image
|
3 |
+
from tensorflow.keras.preprocessing.image import ImageDataGenerator
|
4 |
+
|
5 |
+
# Set paths
|
6 |
+
base_dir = 'data/chest_xray'
|
7 |
+
val_dir = os.path.join(base_dir, 'val')
|
8 |
+
normal_class_dir = os.path.join(val_dir, 'NORMAL')
|
9 |
+
pneumonia_class_dir = os.path.join(val_dir, 'PNEUMONIA')
|
10 |
+
|
11 |
+
|
12 |
+
def augment_images(class_directory, num_augmented_images):
|
13 |
+
datagen = ImageDataGenerator(
|
14 |
+
rescale=1. / 255,
|
15 |
+
rotation_range=20,
|
16 |
+
width_shift_range=0.2,
|
17 |
+
height_shift_range=0.2,
|
18 |
+
shear_range=0.2,
|
19 |
+
zoom_range=0.2,
|
20 |
+
horizontal_flip=True,
|
21 |
+
fill_mode='nearest'
|
22 |
+
)
|
23 |
+
|
24 |
+
generator = datagen.flow_from_directory(
|
25 |
+
directory=os.path.dirname(class_directory), # Parent directory
|
26 |
+
target_size=(150, 150),
|
27 |
+
batch_size=1,
|
28 |
+
class_mode=None,
|
29 |
+
shuffle=False,
|
30 |
+
classes=[os.path.basename(class_directory)] # Specify class if using subdirectory
|
31 |
+
)
|
32 |
+
|
33 |
+
print(f"Found {generator.samples} images in {class_directory}")
|
34 |
+
|
35 |
+
if generator.samples == 0:
|
36 |
+
print("No images found in the directory.")
|
37 |
+
return
|
38 |
+
|
39 |
+
count = 0
|
40 |
+
|
41 |
+
while count < num_augmented_images:
|
42 |
+
try:
|
43 |
+
img_batch = generator.__next__() # Use __next__() to get image batch
|
44 |
+
img = (img_batch[0] * 255).astype('uint8') # Extract the first image in the batch
|
45 |
+
img_pil = Image.fromarray(img)
|
46 |
+
img_path = os.path.join(class_directory, f"augmented_{count}.png")
|
47 |
+
img_pil.save(img_path)
|
48 |
+
count += 1
|
49 |
+
except StopIteration:
|
50 |
+
print("No more images to generate.")
|
51 |
+
break
|
52 |
+
|
53 |
+
print(f"Total augmented images created: {count}")
|
54 |
+
|
55 |
+
|
56 |
+
# Number of augmented images to generate
|
57 |
+
num_augmented_images_normal = 2944 - 3875 # This should be a negative number since NORMAL is already balanced
|
58 |
+
num_augmented_images_pneumonia = 2944 - 1171 # To match the number of NORMAL images
|
59 |
+
|
60 |
+
# Generate augmented images for the NORMAL class
|
61 |
+
augment_images(normal_class_dir, max(num_augmented_images_normal, 0))
|
62 |
+
|
63 |
+
# Generate augmented images for the PNEUMONIA class
|
64 |
+
augment_images(pneumonia_class_dir, num_augmented_images_pneumonia)
|
data_check.py
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from PIL import Image
|
3 |
+
|
4 |
+
# Define the data directories
|
5 |
+
base_dir = 'data/chest_xray'
|
6 |
+
train_dir = os.path.join(base_dir, 'train')
|
7 |
+
val_dir = os.path.join(base_dir, 'val')
|
8 |
+
|
9 |
+
|
10 |
+
# Function to count images in a specific category (e.g., NORMAL, PNEUMONIA)
|
11 |
+
def count_images(directory, category):
|
12 |
+
category_dir = os.path.join(directory, category)
|
13 |
+
count = 0
|
14 |
+
for root, dirs, files in os.walk(category_dir):
|
15 |
+
count += len([f for f in files if f.endswith(('.jpg', '.jpeg', '.png'))])
|
16 |
+
return count
|
17 |
+
|
18 |
+
|
19 |
+
# Function to check for corrupted images in a specific category
|
20 |
+
def check_corrupted_images(directory, category):
|
21 |
+
category_dir = os.path.join(directory, category)
|
22 |
+
corrupted_files = []
|
23 |
+
for root, dirs, files in os.walk(category_dir):
|
24 |
+
for file in files:
|
25 |
+
if file.endswith(('.jpg', '.jpeg', '.png')):
|
26 |
+
try:
|
27 |
+
img = Image.open(os.path.join(root, file))
|
28 |
+
img.verify() # Check if the image can be opened and is not corrupted
|
29 |
+
except (IOError, SyntaxError) as e:
|
30 |
+
corrupted_files.append(os.path.join(root, file))
|
31 |
+
return corrupted_files
|
32 |
+
|
33 |
+
|
34 |
+
# Count images in the train and validation sets
|
35 |
+
train_normal_count = count_images(train_dir, 'NORMAL')
|
36 |
+
train_pneumonia_count = count_images(train_dir, 'PNEUMONIA')
|
37 |
+
val_normal_count = count_images(val_dir, 'NORMAL')
|
38 |
+
val_pneumonia_count = count_images(val_dir, 'PNEUMONIA')
|
39 |
+
|
40 |
+
# Check for corrupted images in the train and validation sets
|
41 |
+
train_normal_corrupted = check_corrupted_images(train_dir, 'NORMAL')
|
42 |
+
train_pneumonia_corrupted = check_corrupted_images(train_dir, 'PNEUMONIA')
|
43 |
+
val_normal_corrupted = check_corrupted_images(val_dir, 'NORMAL')
|
44 |
+
val_pneumonia_corrupted = check_corrupted_images(val_dir, 'PNEUMONIA')
|
45 |
+
|
46 |
+
# Print the results
|
47 |
+
print(f"Training NORMAL images: {train_normal_count}")
|
48 |
+
print(f"Training PNEUMONIA images: {train_pneumonia_count}")
|
49 |
+
print(f"Validation NORMAL images: {val_normal_count}")
|
50 |
+
print(f"Validation PNEUMONIA images: {val_pneumonia_count}")
|
51 |
+
|
52 |
+
print(f"Corrupted images in training NORMAL: {train_normal_corrupted}")
|
53 |
+
print(f"Corrupted images in training PNEUMONIA: {train_pneumonia_corrupted}")
|
54 |
+
print(f"Corrupted images in validation NORMAL: {val_normal_corrupted}")
|
55 |
+
print(f"Corrupted images in validation PNEUMONIA: {val_pneumonia_corrupted}")
|
evaluate_model.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import tensorflow as tf
|
3 |
+
from tensorflow.keras.preprocessing.image import ImageDataGenerator
|
4 |
+
from tensorflow.keras.models import load_model
|
5 |
+
|
6 |
+
base_dir = 'data/chest_xray'
|
7 |
+
val_dir = os.path.join(base_dir, 'val')
|
8 |
+
|
9 |
+
val_datagen = ImageDataGenerator(rescale=1./255)
|
10 |
+
val_generator = val_datagen.flow_from_directory(
|
11 |
+
val_dir,
|
12 |
+
target_size=(150, 150),
|
13 |
+
batch_size=32,
|
14 |
+
class_mode='binary'
|
15 |
+
)
|
16 |
+
|
17 |
+
model = load_model('xray_image_classifier_model.keras')
|
18 |
+
|
19 |
+
loss, accuracy = model.evaluate(val_generator)
|
20 |
+
print(f'Validation Loss: {loss:.4f}')
|
21 |
+
print(f'Validation Accuracy: {accuracy:.4f}')
|
model.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import tensorflow as tf
|
2 |
+
from tensorflow.keras import layers, models
|
3 |
+
from tensorflow.keras.applications import InceptionV3
|
4 |
+
|
5 |
+
def create_model():
|
6 |
+
base_model = InceptionV3(weights='imagenet', include_top=False, input_shape=(150, 150, 3))
|
7 |
+
base_model.trainable = False # Freezing the base model layers
|
8 |
+
|
9 |
+
model = models.Sequential([
|
10 |
+
base_model,
|
11 |
+
layers.GlobalAveragePooling2D(),
|
12 |
+
layers.Dense(512, activation='relu'),
|
13 |
+
layers.Dropout(0.5),
|
14 |
+
layers.Dense(1, activation='sigmoid')
|
15 |
+
])
|
16 |
+
|
17 |
+
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
|
18 |
+
loss='binary_crossentropy',
|
19 |
+
metrics=['accuracy'])
|
20 |
+
return model
|
poetry.lock
ADDED
The diff for this file is too large to render.
See raw diff
|
|
pyproject.toml
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[tool.poetry]
|
2 |
+
name = "xray-image-classifier"
|
3 |
+
version = "0.1.0"
|
4 |
+
description = ""
|
5 |
+
authors = ["Your Name <[email protected]>"]
|
6 |
+
readme = "README.md"
|
7 |
+
|
8 |
+
[tool.poetry.dependencies]
|
9 |
+
python = "3.11.8"
|
10 |
+
tensorflow = "2.15.1"
|
11 |
+
keras = "^2.15.0"
|
12 |
+
numpy = "^1.23.5"
|
13 |
+
pandas = "^2.2.2"
|
14 |
+
matplotlib = "^3.9.2"
|
15 |
+
jupyter = "^1.0.0"
|
16 |
+
scipy = "^1.14.1"
|
17 |
+
[tool.poetry.group.dev.dependencies]
|
18 |
+
pytest = "^8.3.2"
|
19 |
+
ipython = "^8.26.0"
|
20 |
+
autopep8 = "^2.3.1"
|
21 |
+
jupyter = "^1.0.0"
|
22 |
+
|
23 |
+
[build-system]
|
24 |
+
requires = ["poetry-core"]
|
25 |
+
build-backend = "poetry.core.masonry.api"
|
train_model.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import numpy as np
|
3 |
+
import tensorflow as tf
|
4 |
+
import matplotlib.pyplot as plt
|
5 |
+
from tensorflow.keras.preprocessing.image import ImageDataGenerator
|
6 |
+
from model import create_model
|
7 |
+
|
8 |
+
base_dir = 'data/chest_xray'
|
9 |
+
train_dir = os.path.join(base_dir, 'train')
|
10 |
+
val_dir = os.path.join(base_dir, 'val')
|
11 |
+
|
12 |
+
train_datagen = ImageDataGenerator(
|
13 |
+
rescale=1./255,
|
14 |
+
rotation_range=20,
|
15 |
+
width_shift_range=0.2,
|
16 |
+
height_shift_range=0.2,
|
17 |
+
shear_range=0.2,
|
18 |
+
zoom_range=0.2,
|
19 |
+
horizontal_flip=True,
|
20 |
+
fill_mode='nearest'
|
21 |
+
)
|
22 |
+
val_datagen = ImageDataGenerator(rescale=1./255)
|
23 |
+
|
24 |
+
train_generator = train_datagen.flow_from_directory(
|
25 |
+
train_dir,
|
26 |
+
target_size=(150, 150),
|
27 |
+
batch_size=32,
|
28 |
+
class_mode='binary'
|
29 |
+
)
|
30 |
+
|
31 |
+
val_generator = val_datagen.flow_from_directory(
|
32 |
+
val_dir,
|
33 |
+
target_size=(150, 150),
|
34 |
+
batch_size=32,
|
35 |
+
class_mode='binary'
|
36 |
+
)
|
37 |
+
|
38 |
+
sample_images, _ = next(train_generator)
|
39 |
+
for i in range(5):
|
40 |
+
plt.subplot(1, 5, i+1)
|
41 |
+
plt.imshow(sample_images[i])
|
42 |
+
plt.axis('off')
|
43 |
+
plt.show()
|
44 |
+
|
45 |
+
model = create_model()
|
46 |
+
|
47 |
+
history = model.fit(
|
48 |
+
train_generator,
|
49 |
+
steps_per_epoch=243,
|
50 |
+
epochs=10,
|
51 |
+
validation_data=val_generator,
|
52 |
+
validation_steps=280,
|
53 |
+
callbacks=[tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)]
|
54 |
+
)
|
55 |
+
|
56 |
+
model.save('xray_image_classifier_model.keras')
|
xray_image_classifier_model.keras
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:ad1c91968bb830cc6f96327676f487e94763aaec1c83be18e6270cb47bf273fa
|
3 |
+
size 100786904
|