| import tensorflow as tf
|
| from tensorflow.keras.preprocessing import image
|
| import numpy as np
|
| import cv2
|
| import os
|
| from PIL import Image
|
|
|
|
|
|
|
|
|
| cnn_model = None
|
| gradcam_model = None
|
| IMAGE_SIZE = (224, 224)
|
| LAST_CONV_LAYER_NAME = 'conv2'
|
|
|
|
|
|
|
|
|
| def load_cnn_model():
|
| """Loads the pre-trained CNN model and prepares Grad-CAM functional model."""
|
| global cnn_model, gradcam_model
|
| try:
|
| if not os.path.exists('cnn_model.h5'):
|
| raise FileNotFoundError("cnn_model.h5 is missing. Run create_dummy_cnn.py first.")
|
|
|
| cnn_model = tf.keras.models.load_model('cnn_model.h5')
|
|
|
|
|
| cnn_model.compile(optimizer='adam', loss='binary_crossentropy', run_eagerly=False)
|
|
|
|
|
| gradcam_model = tf.keras.models.Model(
|
| inputs=cnn_model.input,
|
| outputs=[cnn_model.get_layer(LAST_CONV_LAYER_NAME).output, cnn_model.output]
|
| )
|
|
|
| print(f"✅ CNN model loaded. Last Conv Layer: {LAST_CONV_LAYER_NAME}")
|
| except Exception as e:
|
| print(f"FATAL ERROR: Could not load or compile CNN model: {e}")
|
| cnn_model = None
|
|
|
|
|
| load_cnn_model()
|
|
|
|
|
|
|
|
|
| def get_img_array(img_path, size):
|
| """Utility function to load image and format it for the model."""
|
| img = image.load_img(img_path, target_size=size)
|
| array = image.img_to_array(img)
|
| array = np.expand_dims(array, axis=0)
|
|
|
|
|
| array = tf.cast(array, dtype=tf.float32)
|
|
|
| array /= 255.0
|
| return array
|
|
|
| def predict_xray_risk(img_path):
|
| if cnn_model is None: return 0.0
|
| try:
|
| img_array = get_img_array(img_path, IMAGE_SIZE)
|
| prediction = cnn_model.predict(img_array)[0]
|
| return float(prediction[0])
|
| except Exception as e:
|
| print(f"CNN Prediction Error: {e}")
|
| return 0.0
|
|
|
|
|
|
|
|
|
|
|
| def make_gradcam_heatmap(img_path, pred_index=None):
|
| """Generates the Grad-CAM heatmap array with stability fixes."""
|
| if gradcam_model is None: return np.zeros(IMAGE_SIZE[:2])
|
|
|
| img_array = get_img_array(img_path, IMAGE_SIZE)
|
|
|
| with tf.GradientTape() as tape:
|
| last_conv_output, preds = gradcam_model(img_array)
|
| if pred_index is None:
|
| pred_index = tf.argmax(preds[0])
|
| class_channel = preds[:, pred_index]
|
|
|
| grads = tape.gradient(class_channel, last_conv_output)
|
| pooled_grads = tf.reduce_mean(grads, axis=(0,1,2))
|
|
|
|
|
| last_conv_output_tensor = last_conv_output[0]
|
|
|
|
|
| heatmap = last_conv_output_tensor * pooled_grads
|
| heatmap = tf.reduce_sum(heatmap, axis=-1)
|
|
|
|
|
| heatmap = tf.maximum(heatmap, 0)
|
| max_val = tf.reduce_max(heatmap)
|
|
|
| if max_val == 0: max_val = tf.constant(1e-10, dtype=tf.float32)
|
| heatmap /= max_val
|
|
|
| return heatmap.numpy()
|
|
|
| def save_gradcam_overlay(img_path, heatmap, save_filename, alpha=0.4):
|
| """Overlays the heatmap onto the original image and saves it to the static folder."""
|
|
|
| static_dir = os.path.join(os.getcwd(), 'static')
|
| os.makedirs(static_dir, exist_ok=True)
|
|
|
| save_path = os.path.join(static_dir, save_filename)
|
|
|
|
|
| img = Image.open(img_path).convert('RGB')
|
| heatmap = cv2.resize(heatmap, (img.width, img.height))
|
| heatmap = np.uint8(255 * heatmap)
|
| heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
|
|
|
| img_cv = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
|
|
|
|
|
| superimposed_img = cv2.addWeighted(img_cv, 1-alpha, heatmap, alpha, 0)
|
|
|
| cv2.imwrite(save_path, superimposed_img)
|
| return save_filename
|
|
|
| def generate_and_save_gradcam(original_xray_path):
|
| """Runs the full Grad-CAM pipeline and returns the FILENAME to the saved image."""
|
| if gradcam_model is None: return None
|
| base_name = os.path.basename(original_xray_path)
|
| gradcam_filename = f"gradcam_{base_name}"
|
|
|
| try:
|
| heatmap = make_gradcam_heatmap(original_xray_path, pred_index=0)
|
| saved_file = save_gradcam_overlay(original_xray_path, heatmap, gradcam_filename)
|
| return saved_file
|
| except Exception as e:
|
|
|
| print(f"GRAD-CAM Generation Failed: {e}")
|
| return None |