"""
<h1><center>HuBMAP - Hacking the Kidney</h1>
"""
"""
<h3><center>Let’s understand the problem statement first</h3>

The aim of this competition is to develop a segmentation problem to identify ‘Glomerulus’ in the kidney. For this we have to train a segmentation model that takes PAS kidney image input and identify the segments of glomeruli FTU in the PAS-stained microscopy data.
For this task, we are given historical images of kidney and annotation information representing the glomerular segmentation.
I believe I used some terminologies, that are confusing. So, lets understand these terms and get some Domain Knowledge.

"""
"""
<h3><center>Domain Knowledge</h3>
"""
"""
<h4>1. What is Glomerulus?</h4>
First, we understand what ‘Nephron’ is. The nephron is the microscopic structural and functional unit of the kidney. It is composed of a renal corpuscle and a renal tubule. The renal corpuscle consists of a tuft of capillaries called a glomerulus and an encompassing Bowman's capsule. 
Glomerulus a cluster of nerve endings, spores, or small blood vessels, in particular a cluster of capillaries around the end of a kidney tubule, where waste products are filtered from the blood.



In short, each nephron in your kidneys has a microscopic filter, called a glomerulus that is constantly filtering your blood.

"""
"""
<h4>2. PAS (Periodic acid-Schiff) Stain Microscopy</h4>
PAS is a histology stain that detects complex sugars in tissue sections. Periodic acid is used to break specific bonds within these sugars. The resulting aldehydes react with the Schiff reagent to produce the purple-magenta color exhibited by these images. Glomeruli can be observed as the circular areas of dark stain.
"""
"""
<h4>3.	FTU (Functional Tissue Unit)</h4>
FTU or functional tissue unit, is a three-dimensional, maximally connected, block of cells centered around a capillary, such that each cell in this block is within diffusion distance from any other cell in the same block.
"""
"""
<h3><center>Let’s understand the DATA now,</h3>
The Dataset is comprised of very large TIFF files (TIFF is described later in the notebook).

•	The training set has 8 files.

•	The public test set has 5 files.

•	The private test set is larger than the public test set. I suppose there will be 7 files.

The train set includes annotations in both RLE-encoded (RLE is explained later in the notebook) and unencoded (JSON) forms. The annotations denote segmentations of glomeruli.
Both training and public test sets include anatomical structure segmentations. I suppose this can be used for pretraining.
JSON files are structured as follows

•	A type (Feature) and object type id (PathAnnotationObject). Note that these fields are the same between all files and do not offer signal.

•	A geometry containing a Polygon with coordinates for the feature's enclosing volume

•	Additional properties, including the name and color of the feature in the image.

•	The IsLocked field is the same across file types (locked in glomerulus, unlocked for anatomical structure) and is not signal bearing.
"""
"""
<h4>TIFF File Format</h4>
Tag Image File Format, abbreviated TIFF or TIF, is a computer file format for storing raster graphics images, popular among graphic artists, the publishing industry, and photographers. TIFF is a flexible, adaptable file format for handling images and data within a single file, by including the header tags (size, definition, image-data arrangement, applied image compression) defining the image's geometry.
"""
"""
<h4>RLE</h4>
The masks provided in the train.csv is in Running Length Encoding format. This encoding comes in pairs of pixel values as follows:

1.	The starting pixel.
2.	Number of pixels from the starting pixel.

So, to specify 10 pixels starting from pixel number 200 would be written as: 200 10
"""
"""
<h3><center>Evaluation Metric</h3>
"""
"""
<h4>Dice Coefficient</h4>
Dice coefficient is a statistical tool which measures the similarity between two sets of data. This index has become arguably the most broadly used tool in the validation of image segmentation algorithms created with AI, but it is a much more general concept which can be applied sets of data for a variety of applications including NLP.
The Dice coefficient can be used to compare the pixel-wise agreement between a predicted segmentation and its corresponding ground truth.
"""
"""
<h4>Jaccard Score</h4>
The Jaccard similarity index (sometimes called the Jaccard similarity coefficient) compares members for two sets to see which members are shared and which are distinct. It’s a measure of similarity for the two sets of data, with a range from 0% to 100%. The higher the percentage, the more similar the two populations. Although it’s easy to interpret, it is extremely sensitive to small samples sizes and may give erroneous results, especially with very small samples or data sets with missing observations.
The formula to find the Index is:
Jaccard Index = (the number in both sets) / (the number in either set) * 100
"""
"""
<h4>Relation between Dice Coefficient and Jaccard Score</h4>
<h4><center>J=D/2-D</h4>

"""
"""
<h3><center>Let’s do the EDA now,</h3>
"""
# Libraries
import cv2
import datetime
import gc
import glob
import math
import matplotlib.pyplot as plt
import numpy as np
import os
import pandas as pd
import skimage.morphology
import sys
import tensorflow as tf
import tifffile
#Parameters
base_path = '../input/hubmap-kidney-segmentation'

plot_full_image = True

# Number of glomeruli to display for each image
num_glom_display = 5

# Number of glomberuli to save as tiff files.
num_glom_save = 5

glob_scale = 0.25
# Utility Functions
def rle_to_image(rle_mask, image_shape):
    """
    Converts an rle string to an image represented as a numpy array.
    Reference: https://www.kaggle.com/paulorzp/rle-functions-run-lenght-encode-decode

    :param rle_mask: string with rle mask.
    :param image_shape: (width, height) of array to return
    :return: Image as a numpy array. 1 = mask, 0 = background.
    """

    # Processing
    s = rle_mask.split()
    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]
    starts -= 1
    ends = starts + lengths
    image = np.zeros(image_shape[0] * image_shape[1], dtype=np.uint8)
    for lo, hi in zip(starts, ends):
        image[lo:hi] = 1

    return image.reshape(image_shape).T
"""
<h4>File Structure</h4>
The files in the root of the dataset are shown below. The dataset consists of 2 directories that contain training and test images and 3 csv-files with additional information about the images.
"""
#Directory Contents
print('\n'.join(os.listdir(base_path)))
# Training Images
train_files = sorted(glob.glob(os.path.join(base_path, 'train/*.tiff')))
print(f'Number of training images: {len(train_files)}')
print('\n'.join(train_files))
#Test Images
test_files = sorted(glob.glob(os.path.join(base_path, 'test/*.tiff')))
print(f'Number of test images: {len(test_files)}')
print('\n'.join(test_files))
#Train CSV
#The masks indicating a glomeruli FTUs are stored in rle format in the train.csv for each training image id.
df_train = pd.read_csv(os.path.join(base_path, 'train.csv'))
display(df_train)
# Sample_Submission.csv
#The sample_submission.csv files shows the format of the submissions files consisting of the test image id and an rle encoded masks.
df_submission = pd.read_csv(os.path.join(base_path,'sample_submission.csv'))
display(df_submission)
# Paitient Data
#HuBMAP-20-dataset_information.csv contains additional information about each image such as image size and anonymized patient data.
df_info = pd.read_csv(os.path.join(base_path,'HuBMAP-20-dataset_information.csv'))
display(df_info)
"""
<h3><center>Training Image Analysis</h3>
<h4>Width and Height Distribution</h4>

The training images do not have consistent dimensions. This has to be corrected when loading the images. They have on of the following shapes:

[height, width, channel]
    
[channel, height, width]

[1, 1, channel, height, width]
"""
for f in train_files + test_files:
    image = tifffile.imread(f)
    print(f'Image {f} shape: {image.shape}', flush=True)
    del image
    gc.collect()
#The size of the images varies greatly as well.
plt.scatter(df_info['width_pixels'], df_info['height_pixels'])
plt.title('Image Height and Width')
plt.xlabel('Width')
plt.ylabel('Height')
plt.xlim(0, df_info['width_pixels'].max() * 1.1)
plt.ylim(0, df_info['height_pixels'].max() * 1.1)
plt.grid()
"""
<h4>Image Utilitity Functions</h4>
"""
def overlay_image_mask(image, mask, mask_color=(0,255,0), alpha=1.0):
    im_f= image.astype(np.float32)
#     if mask.ndim == 2:
#         mask = np.expand_dims(mask,-1)        
    mask_col = np.expand_dims(np.array(mask_color)/255.0, axis=(0,1))
    return (im_f + alpha * mask * (np.mean(0.8 * im_f + 0.2 * 255, axis=2, keepdims=True) * mask_col - im_f)).astype(np.uint8)


def overlay_image_mask_original(image, mask, mask_color=(0,255,0), alpha=1.0):
    return  np.concatenate((image, overlay_image_mask(image, mask)), axis=1)

def get_image_id(image_file):
    return os.path.splitext(os.path.split(image_file)[1])[0]


def read_image(image_file, scale=1.0):
    image = tifffile.imread(image_file).squeeze()
    if image.shape[0] == 3:
        image = np.transpose(image, (1,2,0))
    
    orig_shape = image.shape
    if scale != 1.0:
        image = cv2.resize(image, (0,0), fx=scale, fy=scale)
    return image, orig_shape


def read_mask(image_file, image_shape, scale=1.0):
    image_id = get_image_id(image_file)
    train_info = df_train.loc[df_train['id'] == image_id]
    rle = train_info['encoding'].values[0] if len(train_info) > 0 else None
    if rle is not None:
        mask = rle_to_image(rle, (image_shape[1], image_shape[0]))
        if scale != 1.0:
            mask = cv2.resize(mask, (0,0), fx=scale, fy=scale)
        return np.expand_dims(mask,-1)
    else:
        return None        

    
def read_image_mask(image_file, scale=1.0):
    image, image_shape = read_image(image_file, scale)
    mask = read_mask(image_file, image_shape, scale)
    return image, mask


def get_tile(image, mask, x, y, tile_size, scale=1.0):
    x = round(x * scale)
    y = round(y * scale)
    size = int(round(tile_size / 2 * scale))
    image_s = image[y-size:y+size, x-size:x+size, :] 
    mask_s = mask[y-size:y+size, x-size:x+size, :]
    return image_s, mask_s


def get_particles(mask, scale=1.0):
    num, labels, stats, centroids = cv2.connectedComponentsWithStats(mask)
    df_particles = pd.DataFrame(dict(zip(['x','y','left','top','width','height','area'],
                               [(centroids[1:,0]) / scale,
                                (centroids[1:,1]) / scale,
                                (stats[1:,cv2.CC_STAT_LEFT]) / scale,
                                (stats[1:,cv2.CC_STAT_TOP]) / scale,
                                (stats[1:,cv2.CC_STAT_WIDTH]) / scale,
                                (stats[1:,cv2.CC_STAT_HEIGHT]) / scale,
                                (stats[1:,cv2.CC_STAT_AREA]) / (scale * scale)])))
    df_particles.sort_values(['x','y'], inplace=True, ignore_index=True)
    df_particles['no'] = range(len(df_particles))
    return df_particles


def analyze_image(image_file):
    image_id = get_image_id(image_file)
    image, image_shape = read_image(image_file, glob_scale)
    mask = read_mask(image_file, image_shape, glob_scale)

    mask_full = read_mask(image_file, image_shape, scale=1.0)
    df_glom = get_particles(mask_full, scale=1.0)
    df_glom['id'] = image_id
    del mask_full
    gc.collect()
    
    info = df_info[df_info['image_file'] == f'{image_id}.tiff']
    print(f'Image ID:        {image_id:}')
    print(f'Image Size:      {info["width_pixels"].values[0]} x {info["height_pixels"].values[0]}')
    print(f'Patient No:      {info["patient_number"].values[0]}')
    print(f'Sex:             {info["sex"].values[0]}')
    print(f'Age:             {info["age"].values[0]}')
    print(f'Race:            {info["race"].values[0]}')
    print(f'Height:          {info["height_centimeters"].values[0]} cm')
    print(f'Weight:          {info["weight_kilograms"].values[0]} kg')
    print(f'BMI:             {info["bmi_kg/m^2"].values[0]} kg/m^2')
    print(f'Laterality:      {info["laterality"].values[0]}')
    print(f'Percent Cortex:  {info["percent_cortex"].values[0]} %')
    print(f'Percent Medulla: {info["percent_medulla"].values[0]} %')
    
    # Plot full image
    if plot_full_image:
        scale = 0.1
        image_small = cv2.resize(image, (0,0), fx=scale, fy=scale)
        mask_small = cv2.resize(mask, (0,0), fx=scale, fy=scale)
        mask_small = np.expand_dims(mask_small,-1) 
    
        plt.figure(figsize=(16, 16))
        plt.imshow(overlay_image_mask(image_small, mask_small))
        plt.axis('off')

    # Plot glomeruli images
    fig_cols = 5
    fig_rows = int(math.ceil(num_glom_display/fig_cols))
    plt.figure(figsize=(4 * fig_cols, 4 * fig_rows))
    if num_glom_save > 0 and not os.path.exists(image_id):
        os.mkdir(image_id)
    for i in range(min(max(num_glom_display, num_glom_save), len(df_glom))):
        image_s, mask_s = get_tile(image,mask, df_glom['x'][i], df_glom['y'][i], 1000, scale=glob_scale)
        ovl = overlay_image_mask(image_s, mask_s)
        if i < num_glom_display:
            plt.subplot(fig_rows, fig_cols, i+1)
            plt.imshow(ovl)
            plt.axis('off')
        if i < num_glom_save:
            cv2.imwrite(f'{image_id}_{i:03}.png', cv2.cvtColor(ovl, cv2.COLOR_RGB2BGR))    
    
    del image, mask
    gc.collect()
    return df_glom


def plot_glom(df, image_id, glom_no):
    image, mask = read_image_mask(os.path.join(base_path, f'train/{image_id}.tiff'), scale=glob_scale)
    glom = df.loc[(df['id'] == image_id) & (df['no'] == glom_no)]
    im, ma = get_tile(image, mask, glom['x'].iloc[0], glom['y'].iloc[0], 1000, scale=glob_scale)
    del image, mask
    gc.collect()
    plt.figure(figsize=(16,8))
    plt.imshow(overlay_image_mask_original(im, ma))
    plt.title(f'Image: {image_id}, Glomeruli No: {glom_no}, Area: {glom["area"].iloc[0]}')
"""
<h3>Training Images With Glomerulis</h3>
"""
df_glom = pd.DataFrame()
df_glom = df_glom.append(analyze_image(train_files[0]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[1]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[2]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[3]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[4]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[5]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[6]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[7]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[8]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[9]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[10]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[11]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[12]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[13]), ignore_index=True)
df_glom = df_glom.append(analyze_image(train_files[14]), ignore_index=True)
"""
<h3>Glomerulis</h3>
"""
"""
<h4>Basic Statistics</h4>
"""
df_glom.to_csv('glomeruli.csv')
display(df_glom)
df_glom.describe()
"""
<h4>Glomerulis Per Image</h4>
"""
g = df_glom.groupby('id')
plt.bar(g.size().index, g.size().values)
plt.title('Number of Glomerulis in Image')
plt.xticks(rotation=90)
plt.grid()
"""
<h4>Glomeruli Width, Height and Area Distribution</h4>
"""
plt.figure(figsize=(20,5))
plt.subplot(1,3,1)
plt.hist(df_glom['width'], bins=40, density=True)
plt.title('Width Distribution')
plt.grid()
plt.subplot(1,3,2)
plt.hist(df_glom['height'], bins=40, density=True)
plt.title('Height Distribution')
plt.grid()
plt.subplot(1,3,3)
plt.hist(df_glom['area'], bins=40, density=True)
plt.title('Area Distribution')
plt.grid()
"""
<h4>Glomerulis by Size</h4>
"""
df_glom.sort_values('area', inplace=True)
df_glom
"""
<h4>Five Smallest Glomerulis</h4>
"""
for i in range(5):
    plot_glom(df_glom, df_glom['id'].iloc[i], df_glom['no'].iloc[i])
"""
<h4>Five Largest Glomerulis</h4>
"""
for i in range(len(df_glom)-5, len(df_glom)):
    plot_glom(df_glom, df_glom['id'].iloc[i], df_glom['no'].iloc[i])
"""
<h3><center>SUB EffUNet5 + TPU EfficientUNet 512x512</h3>
"""
"""
In the code below I will try to use TPU EfficientUNet 512x512 with freeze-pretrained SUB EffUNet5. This will help to improve the existing algorithms used to detect functional tissue units (FTUs) across different tissue preparation pipelines.
"""
mod_path = '/kaggle/input/hubmap-tf-with-tpu-efficientunet-512x512-train/'
import yaml
import pprint
with open(mod_path+'params.yaml') as file:
    P = yaml.load(file, Loader=yaml.FullLoader)
    pprint.pprint(P)
    
THRESHOLD = 0.4
WINDOW = 1024
MIN_OVERLAP = 300
NEW_SIZE = P['DIM']

SUBMISSION_MODE = 'PUBLIC_TFREC' 
# 'PUBLIC_TFREC' = use created tfrecords for public test set with MIN_OVERLAP = 300 tiling 1024-512, ignore other (private test) data
# 'FULL' do not use tfrecords, just full submission 

CHECKSUM = True # compute mask sum for each image
# METRICS

import json

with open(mod_path + 'metrics.json') as json_file:
    M = json.load(json_file)
print('Model run datetime: '+M['datetime'])
print('OOF val_dice_coe: ' + str(M['oof_dice_coe']))
! pip install ../input/kerasapplications/keras-team-keras-applications-3b180cb -f ./ --no-index -q
! pip install ../input/efficientnet/efficientnet-1.1.0/ -f ./ --no-index -q
import numpy as np
import pandas as pd
import os
import glob
import gc

import rasterio
from rasterio.windows import Window

import pathlib
from tqdm.notebook import tqdm
import cv2

import tensorflow as tf
import efficientnet as efn
import efficientnet.tfkeras

import os, glob, gc
import json

osj = os.path.join
def rle_encode_less_memory(img):
    pixels = img.T.flatten()
    pixels[0] = 0
    pixels[-1] = 0
    runs = np.where(pixels[1:] != pixels[:-1])[0] + 2
    runs[1::2] -= runs[::2]
    return ' '.join(str(x) for x in runs)

def make_grid(shape, window=256, min_overlap=32):
    """
        Return Array of size (N,4), where N - number of tiles,
        2nd axis represente slices: x1,x2,y1,y2 
    """
    x, y = shape
    nx = x // (window - min_overlap) + 1
    x1 = np.linspace(0, x, num=nx, endpoint=False, dtype=np.int64)
    x1[-1] = x - window
    x2 = (x1 + window).clip(0, x)
    ny = y // (window - min_overlap) + 1
    y1 = np.linspace(0, y, num=ny, endpoint=False, dtype=np.int64)
    y1[-1] = y - window
    y2 = (y1 + window).clip(0, y)
    slices = np.zeros((nx,ny, 4), dtype=np.int64)
    
    for i in range(nx):
        for j in range(ny):
            slices[i,j] = x1[i], x2[i], y1[j], y2[j]    
    return slices.reshape(nx*ny,4)

def global_shift_mask(maskpred1, y_shift, x_shift):
    """
    applies a global shift to a mask by 
    padding one side and cropping from the other
    """
    if y_shift < 0 and x_shift >=0:
        maskpred2 = np.pad(maskpred1, 
                           [(0,abs(y_shift)), (abs(x_shift), 0)], 
                           mode='constant', constant_values=0)
        maskpred3 = maskpred2[abs(y_shift):, :maskpred1.shape[1]]
    elif y_shift >=0 and x_shift <0:
        maskpred2 = np.pad(maskpred1, 
                           [(abs(y_shift),0), (0, abs(x_shift))], 
                           mode='constant', constant_values=0)
        maskpred3 = maskpred2[:maskpred1.shape[0], abs(x_shift):]
    elif y_shift >=0 and x_shift >=0:
        maskpred2 = np.pad(maskpred1,
                           [(abs(y_shift),0), (abs(x_shift), 0)], 
                           mode='constant', constant_values=0)
        maskpred3 = maskpred2[:maskpred1.shape[0], :maskpred1.shape[1]]
    elif y_shift < 0 and x_shift < 0:
        maskpred2 = np.pad(maskpred1, 
                           [(0, abs(y_shift)), (0, abs(x_shift))], 
                           mode='constant', constant_values=0)
        maskpred3 = maskpred2[abs(y_shift):, abs(x_shift):]
    return maskpred3
##MODEL
identity = rasterio.Affine(1, 0, 0, 0, 1, 0)
fold_models_1 = []
for fold_model_path in glob.glob(mod_path+'*.h5'):
    fold_models_1.append(tf.keras.models.load_model(fold_model_path,compile = False))
print(len(fold_models_1))
AUTO = tf.data.experimental.AUTOTUNE
image_feature = {
    'image': tf.io.FixedLenFeature([], tf.string),
    'x1': tf.io.FixedLenFeature([], tf.int64),
    'y1': tf.io.FixedLenFeature([], tf.int64)
}
def _parse_image(example_proto):
    example = tf.io.parse_single_example(example_proto, image_feature)
    image = tf.reshape( tf.io.decode_raw(example['image'],out_type=np.dtype('uint8')), (P['DIM'],P['DIM'], 3))
    return image, example['x1'], example['y1']

def load_dataset(filenames, ordered=True):
    ignore_order = tf.data.Options()
    if not ordered:
        ignore_order.experimental_deterministic = False
    dataset = tf.data.TFRecordDataset(filenames)
    dataset = dataset.with_options(ignore_order)
    dataset = dataset.map(_parse_image)
    return dataset

def get_dataset(FILENAME):
    dataset = load_dataset(FILENAME)
    dataset  = dataset.batch(64)
    dataset = dataset.prefetch(AUTO)
    return dataset
debug = True # True False
n_debug_images = 1 if debug else 1000000000
n_debug_slices = 20 if debug else 1000000000

# whether to run prediction when committing. WILL RUN predictions during submission in any case
do_predict = False  if not debug else True

models_dir = '../input/hubmap-models-cv-08848-pl-0847'
model_filepaths = [ os.path.join(models_dir, f"model-fold-{i}.h5") for i in range(4)]

assert len(model_filepaths)==len(np.unique(model_filepaths))
#folds_to_predict = [i for (i, fn) in enumerate(model_filepaths) if os.path.isfile(fn)]
model_dirnames = [os.path.dirname(filepath) for filepath in model_filepaths]

#check_order = [fn.split('.')[-2].split('-')[-1] == i for (i,fn) in enumerate(model_filepaths) if fn.strip()!='']
#assert np.sum(check_order)==0, 'models should be in folds order or empty string'

import yaml
import pprint
with open(osj(model_dirnames[0],'params.yaml')) as file:
    P = yaml.load(file, Loader=yaml.FullLoader)
    pprint.pprint(P)

THRESHOLD = 0.3
WINDOW = 1024
MIN_OVERLAP = 32
NEW_SIZE = P['DIM']

assert sum([not os.path.isfile(path_) for path_ in model_filepaths]) == 0
print("\n Number of models:: {}".format(len(model_filepaths)))
ave_score = 0
for i, m_path in enumerate(model_filepaths):
    fold_ = int(m_path.split('.')[-2].split('-')[-1])
    with open(osj(model_dirnames[i],'metrics.json')) as json_file:
        M = json.load(json_file)
    print(f"\n ----------- \nModel {model_dirnames[i].split('/')[-1]}" +
          '\nval_dice_coe: '+ str(round(M['val_dice_coe'][fold_], 5)) +
          '\tval_loss: ' + str(round(M['val_loss'][fold_], 5)) +
          '\tval_accuracy: '+ str(round(M['val_accuracy'][fold_], 5))
          )



for model_group in np.unique(model_dirnames):
    with open(osj(model_group,'metrics.json')) as json_file:
        M = json.load(json_file)
        ave_dice = np.mean(M['val_dice_coe']) 
    ave_loss = np.mean(M['val_loss'])  # /len(folds_to_predict)
    ave_accuracy = np.mean(M['val_accuracy'])
    print(f"\n ============ MODEL GROUP {model_group} ==============")
    print(" ------------ \nAVERAGE DICE SCORE = {}".format(round(ave_dice, 5)))
    print(" ------------ \nAVERAGE VALIDATION LOSS = {}".format(round(ave_loss, 5)))
    print(" ------------ \nAVERAGE VALIDATION ACCURACY = {}".format(round(ave_accuracy, 5)))
%%time
if do_predict:
    identity = rasterio.Affine(1, 0, 0, 0, 1, 0)
    fold_models_2 = []
    
    for fold_model_path in model_filepaths:
        fold_models_2.append(tf.keras.models.load_model(fold_model_path,compile = False))
    print(len(fold_models_2))
"""
<h3>References</h3>

https://www.kaggle.com/kwk100/hubmap-exploratory-data-analysis-eda

https://www.kaggle.com/roydatascience/hubmap-sub-effunet5-tpu-efficientunet-512x512
"""