<jupyter_start><jupyter_text>Case Study - 2 (RSNA Pneumonia Detection Challenge) By <NAME> (<EMAIL>)   1. Initialization<jupyter_code>import numpy as np
import tensorflow as tf
import cv2
import pandas as pd
from tensorflow.keras import backend as K
from tensorflow.keras.models import load_model
import matplotlib.pyplot as plt

# max number of bounding boxes to be detected
yolo_max_boxes = 5
# IOU threshold
yolo_iou_threshold = 0.5
# Objectness threshold
yolo_score_threshold = 0.2<jupyter_output><empty_output><jupyter_text>2. Utility Functions & Preprocessing * **Metric for ChexNet calssification model**<jupyter_code># function to calculate recall
# refernce https://datascience.stackexchange.com/a/45166
# it is used as a metric for our ChexNet classification model
def recall_m(y_true, y_pred):
        true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
        possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
        recall = true_positives / (possible_positives + K.epsilon())
        return recall

# function to calculate precision
def precision_m(y_true, y_pred):
        true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
        predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
        precision = true_positives / (predicted_positives + K.epsilon())
        return precision

# function to calculate micro averaged f1_score
def f1_m(y_true, y_pred):
    precision = precision_m(y_true, y_pred)
    recall = recall_m(y_true, y_pred)
    return 2*((precision*recall)/(precision+recall+K.epsilon()))<jupyter_output><empty_output><jupyter_text>* **Functions Function used inside lambda layer of YoloV3ChexNet model**<jupyter_code># Function used inside lambda layer of YoloV3ChexNet model to calculate bounding box info
def yolo_boxes(pred, anchors, classes):
    # pred: (batch_size, grid, grid, anchors, (x, y, w, h, obj, ...classes))
    grid_size = tf.shape(pred)[1:3]
    box_xy, box_wh, objectness, class_probs = tf.split(
        pred, (2, 2, 1, classes), axis=-1)

    box_xy = tf.sigmoid(box_xy)
    objectness = tf.sigmoid(objectness)
    class_probs = tf.sigmoid(class_probs)
    pred_box = tf.concat((box_xy, box_wh), axis=-1)  # original xywh for loss

    # !!! grid[x][y] == (y, x)
    grid = tf.meshgrid(tf.range(grid_size[1]), tf.range(grid_size[0]))
    grid = tf.expand_dims(tf.stack(grid, axis=-1), axis=2)  # [gx, gy, 1, 2]

    box_xy = (box_xy + tf.cast(grid, tf.float32)) / \
        tf.cast(grid_size, tf.float32)
    box_wh = tf.exp(box_wh) * anchors

    box_x1y1 = box_xy - box_wh / 2
    box_x2y2 = box_xy + box_wh / 2
    bbox = tf.concat([box_x1y1, box_x2y2], axis=-1)

    return bbox, objectness, class_probs, pred_box


# Function used inside lambda layer of YoloV3ChexNet model to perform non-maximum supression
def yolo_nms(outputs, anchors, masks, classes):
    # boxes, conf, type
    global  yolo_max_boxes, yolo_iou_threshold, yolo_score_threshold
    b, c, t = [], [], []

    for o in outputs:
        b.append(tf.reshape(o[0], (tf.shape(o[0])[0], -1, tf.shape(o[0])[-1])))
        c.append(tf.reshape(o[1], (tf.shape(o[1])[0], -1, tf.shape(o[1])[-1])))
        t.append(tf.reshape(o[2], (tf.shape(o[2])[0], -1, tf.shape(o[2])[-1])))

    bbox = tf.concat(b, axis=1)
    confidence = tf.concat(c, axis=1)
    class_probs = tf.concat(t, axis=1)

    scores = confidence * class_probs
    boxes, scores, classes, valid_detections = tf.image.combined_non_max_suppression(
        boxes=tf.reshape(bbox, (tf.shape(bbox)[0], -1, 1, 4)),
        scores=tf.reshape(
            scores, (tf.shape(scores)[0], -1, tf.shape(scores)[-1])),
        max_output_size_per_class= yolo_max_boxes,
        max_total_size = yolo_max_boxes,
        iou_threshold = yolo_iou_threshold,
        score_threshold = yolo_score_threshold
    )

    return boxes, scores, classes, valid_detections<jupyter_output><empty_output><jupyter_text>* **Functions used for image preprocessing and post-prediction processing**<jupyter_code>def transform_images(x_train, size):
    x_train = tf.image.resize(x_train, (size, size))
    x_train = x_train / 255
    return x_train

def draw_outputs(img, outputs, class_names):
    boxes, objectness, classes, nums = outputs
    boxes, objectness, classes, nums = boxes[0], objectness[0], classes[0], nums[0]
    wh = np.flip(img.shape[0:2])
    for i in range(nums):
        x1y1 = tuple((np.array(boxes[i][0:2]) * wh).astype(np.int32))
        x2y2 = tuple((np.array(boxes[i][2:4]) * wh).astype(np.int32))
        img = cv2.rectangle(img, x1y1, x2y2, (255, 0, 0), 2)
        img = cv2.putText(img, '{} {:.4f}'.format(
            class_names[int(classes[i])], objectness[i]),
            x1y1, cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, (0, 0, 255), 2)
    return img<jupyter_output><empty_output><jupyter_text>* **Function to draw ground truth bounding boxes on images**<jupyter_code>def original(data):
    """
    Method to draw single patient with bounding box(es) if present 
    """
    im = cv2.imread(data['img_dir'])
    # --- Add boxes with random color if present
    for box in data['boxes']:
        rgb = np.floor(np.random.rand(3) * 256).astype('int')
        im = overlay_box(im=im, box=box, rgb=rgb, stroke=6)

    return im

def overlay_box(im, box, rgb, stroke=1):
    """
    Method to overlay single box on image

    """
    # --- Convert coordinates to integers
    box = [int(b) for b in box]
    
    # --- Extract coordinates
    y1, x1, height, width = box
    y2 = y1 + height
    x2 = x1 + width

    im[y1:y1 + stroke, x1:x2] = rgb
    im[y2:y2 + stroke, x1:x2] = rgb
    im[y1:y2, x1:x1 + stroke] = rgb
    im[y1:y2, x2:x2 + stroke] = rgb

    return im<jupyter_output><empty_output><jupyter_text>* **Function to read & resize images**<jupyter_code>def read_img(img_dir=None):
    img_raw = tf.image.decode_image(
            open(img_dir, 'rb').read(), channels=3)
    img = tf.expand_dims(img_raw, 0)
    img = transform_images(img, 416)
    return img<jupyter_output><empty_output><jupyter_text>* **Function to transorm data into specified nested dictionary format.**<jupyter_code>def parse_data(df):
    """
    Method to read a CSV file (Pandas dataframe) and parse the 
    data into the following nested dictionary:

      parsed = {
        
        'patientId-00': {
            'jpg': path/to/jpg/file,
            'label': either 0 or 1 for normal or pnuemonia, 
            'boxes': list of box(es)
        },
        'patientId-01': {
            'jpg': path/to/jpg/file,
            'label': either 0 or 1 for normal or pnuemonia, 
            'boxes': list of box(es)
        }, ...

      }

    """
    # --- Define lambda to extract coords in list [y, x, height, width]
    extract_box = lambda row: [row['y'], row['x'], row['height'], row['width']]

    parsed = {}
    for n, row in df.iterrows():
        # --- Initialize patient entry into parsed 
        pid = row['patientId']
        if pid not in parsed:
            parsed[pid] = {
                'img_dir': 'data/RSNA_VOC/JPEGImages/%s.jpg' % pid,
                'label': row['Target'],
                'boxes': []}

        # --- Add box if opacity is present
        if parsed[pid]['label'] == 1:
            parsed[pid]['boxes'].append(extract_box(row))

    return parsed<jupyter_output><empty_output><jupyter_text>* **Function to predict bounding boxes given YoloV3_ChexNet model**<jupyter_code>def detect(classes = 'data/RSNA_VOC/RSNA_VOC.names',
            model=None,
            size=416,
            num_classes=1,
            img=None,
            img_raw=None):
    class_names = [c.strip() for c in open(classes).readlines()]
    boxes, scores, classes, nums = model(img)
    img = cv2.cvtColor(img_raw.numpy(), cv2.COLOR_RGB2BGR)
    img = draw_outputs(img, (boxes, scores, classes, nums), class_names)
    return img<jupyter_output><empty_output><jupyter_text>* **Reading Data**<jupyter_code># load image id's
ID_s = np.loadtxt('FINAL/positive_val.txt',dtype='str')
# load bounding box information 
train_data_df = pd.read_csv('data/stage_2_train_labels.csv')
parsed = parse_data(train_data_df)
# positive patient id from validation dataset
ID_s[:20]
# whole parsed data
parsed<jupyter_output><empty_output><jupyter_text>3. Loading pretrained models<jupyter_code># loading ChexNet classification model (with trained weights) 
chexnet_classifier = load_model('models and weights/chexnet_model.hdf5',custom_objects={'f1_m':f1_m})

# loading model yoloV3ChexNet model (with trained weights)
yolov3_chexnet = tf.keras.models.load_model('models and weights/yolov3_chexnet_model.hdf5')<jupyter_output>WARNING:tensorflow:No training configuration found in the save file, so the model was *not* compiled. Compile it manually.<jupyter_text>4. Function_1(classify_detect)* **It first classify an image as POSITIVE (containing opacity) or NEGATIVE (not containing opacity) using ChexNet classification model, if an image is predicted to be POSITIVE then it detects the opacity bounding boxes using YoloV3_ChexNet model.**<jupyter_code>def classify_detect(classifier,detector,data,i_d):
    '''
    Parameters:
                classifier : image classification model (ChexNet)
                detector : object detection model (YoloV3_ChexNet)
                data: dictionary containing image directory  & bounding box information
                i_d: patient id (unique)
    '''
    # readin raw image
    img_raw = tf.image.decode_image(
            open(data[i_d]['img_dir'], 'rb').read(), channels=3)
    # expanding dimensions
    img = tf.expand_dims(img_raw, 0)
    # transforming image
    img = transform_images(img, 416)
    # classifier prediction
    probability = chexnet_classifier.predict(img)[0][0]
    if probability>=0.5:
        print('Opacity Detected !!')
        # detector prediction
        detected = detect(model = detector,img=img,img_raw=img_raw)
        plt.figure(figsize=(10,6))
        #plt.subplot(1,2,1)
        plt.title('YoloV3(ChexNet) Predicted')
        plt.grid(False)
        plt.imshow(detected)
        plt.show()
    else:
        print('No Opacity Detected !!')<jupyter_output><empty_output><jupyter_text>* **Positive Case**<jupyter_code>classify_detect(classifier=chexnet_classifier,
                detector=yolov3_chexnet,
                data=parsed,
                i_d='f21a2acc-e6f1-4132-b094-83c9e94a5e7d')<jupyter_output>Opacity Detected !!<jupyter_text>* **Negative Case**<jupyter_code>classify_detect(classifier=chexnet_classifier,
                detector=yolov3_chexnet,
                data=parsed,
                i_d='0004cfab-14fd-4e49-80ba-63a80b6bddd6')<jupyter_output>No Opacity Detected !!<jupyter_text>5. Function_2 (detection)* **It detects  & displays the opacity in images along with the ground truth bounding boxes.**<jupyter_code>def detection(model,data,i_d):
    '''
    Parameters: 
                model : trained YoloV3_ChexNet object detection model
                data: data: dictionary containing image directory  & bounding box information
                i_d: patient id (unique)
    '''
    img_raw = tf.image.decode_image(
            open(data[i_d]['img_dir'], 'rb').read(), channels=3)
    img = tf.expand_dims(img_raw, 0)
    img = transform_images(img, 416)
    predicted_img = detect(model=model,img=img,img_raw=img_raw)
    original_img = original(data[i_d])
    plt.figure(figsize=(10,6))
    plt.subplot(1,2,1)
    plt.title('Ground Truth')
    plt.grid(False)
    plt.imshow(original_img)
    plt.subplot(1,2,2)
    plt.title('YoloV3(ChexNet) Predicted')
    plt.grid(False)
    plt.imshow(predicted_img)
    plt.show()
# detection e.g - 1
detection(model=yolov3_chexnet,data=parsed,i_d='f2a78be0-4397-4b01-8357-c2596406edd1')
# detection e.g - 2
detection(model=yolov3_chexnet,data=parsed,i_d='f27e1121-c394-4577-80c6-762f47e916b2')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12850526315789473, 'fraction_numerical': 0.02677894736842105, 'mean_word_length': 3.4412864622288706, 'pattern_counts': {'":': 0, '<': 56, '<?xml version=': 0, '>': 57, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '334863', 'license': 'MIT', 'n_tokens_mistral': 4370, 'n_tokens_neox': 4172, 'n_words': 922, 'path': 'FINAL.ipynb', 'repo_name': 'junaidnasirkhan/Replacing-YoloV3-Backbone-with-ChexNet-for-Pneumonia-Detection', 'size': '11896'}