# -*- coding: utf-8 -*-
"""
Class definition of YOLO_v3 style detection model on image and video
"""

import colorsys
import sys
from timeit import default_timer as timer

import numpy as np
import cv2

from keras import backend as K
from keras.models import load_model
from keras.layers import Input
from PIL import Image, ImageFont, ImageDraw

import tensorflow as tf
from tensorflow.python.framework import graph_io
from tensorflow.keras.models import load_model

from src.yolo3.model import yolo_eval, yolo_body, tiny_yolo_body
from src.yolo3.utils import letterbox_image
import os
from keras.utils import multi_gpu_model

class YOLO(object):
    _defaults = {
            "model_path": 'model_data/AR10-ep074-loss16.139-val_loss16.430.h5',
            "anchors_path": 'model_data/yolo_anchors.txt',
            "classes_path": 'model_data/AR10_classes.txt',
            "score": 0.3,
            "iou": 0.45,
            "model_image_size": (416, 416),
            "gpu_num": 1,
    }

    @classmethod
    def get_defaults(cls, n):
        if n in cls._defaults:
            return cls._defaults[n]
        else:
            return "Unrecognized attribute name '" + n + "'"

    def __init__(self, **kwargs):
        self.__dict__.update(self._defaults)  # set up default values
        self.__dict__.update(kwargs)  # and update with user overrides
        self.yolo_model = None
        self.class_names = self._get_class()
        self.anchors = self._get_anchors()
        self.sess = K.get_session()
        if os.path.expanduser(self.model_path).endswith('h5'):
            self.boxes, self.scores, self.classes = self.generate()
        elif os.path.expanduser(self.model_path).endswith('pb'):
            self.boxes, self.scores, self.classes = self.load_frozen_model()

    def _get_class(self):
        classes_path = os.path.expanduser(self.classes_path)
        with open(classes_path) as f:
            class_names = f.readlines()
        class_names = [c.strip() for c in class_names]
        return class_names

    def _get_anchors(self):
        anchors_path = os.path.expanduser(self.anchors_path)
        with open(anchors_path) as f:
            anchors = f.readline()
        anchors = [float(x) for x in anchors.split(',')]
        return np.array(anchors).reshape(-1, 2)

    def save_frozen_model(self, save_pb_dir='.', save_pb_name='frozen_model.pb',
                          save_pb_as_text=False):

        assert save_pb_name.endswith('pb'), 'Name must have .pb extension'

        session = self.sess
        graph = session.graph
        output = ['boxes', 'scores', 'classes']
        print(f'Model output {output}')
        with graph.as_default():
            print('Freezing session...')
            graphdef_frozen = tf.graph_util.convert_variables_to_constants(session,
                                                                           session.graph_def,
                                                                           output)

            print('Saving graph...')
            graph_io.write_graph(graphdef_frozen, save_pb_dir, save_pb_name,
                                 as_text=save_pb_as_text)
            print(f'Graph saved to: {os.path.join(save_pb_dir, save_pb_name)}')

    def load_frozen_model(self):
        model_path = os.path.expanduser(self.model_path)
        assert model_path.endswith(
            '.pb'), 'Frozen tensorflow model must be a .pb file.'

        with tf.gfile.GFile(model_path, 'rb') as f:
            graph_def = tf.GraphDef()
            graph_def.ParseFromString(f.read())

        # Generate colors for drawing bounding boxes.
        hsv_tuples = [(x / len(self.class_names), 1., 1.)
                      for x in range(len(self.class_names))]
        self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
        self.colors = list(
                map(lambda x: (
                        int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
                    self.colors))
        np.random.seed(10101)  # Fixed seed for consistent colors across runs.
        np.random.shuffle(
                self.colors)  # Shuffle colors to decorrelate adjacent classes.
        np.random.seed(None)  # Reset seed to default.

        tf.graph_util.import_graph_def(graph_def)
        self.input_name = self.sess.graph_def.node[0].name + ':0'
        self.input_image_shape = tf.get_default_graph().get_tensor_by_name(
                'import/image_shape:0')

        boxes_ = tf.get_default_graph().get_tensor_by_name("import/boxes:0")
        scores_ = tf.get_default_graph().get_tensor_by_name("import/scores:0")
        classes_ = tf.get_default_graph().get_tensor_by_name("import/classes:0")

        return boxes_, scores_, classes_

    def generate(self):
        model_path = os.path.expanduser(self.model_path)
        assert model_path.endswith(
            '.h5'), 'Keras model or weights must be a .h5 file.'

        # Load model, or construct model and load weights.
        num_anchors = len(self.anchors)
        num_classes = len(self.class_names)
        is_tiny_version = num_anchors == 6  # default setting
        try:
            self.yolo_model = load_model(model_path, compile=False)
            print('Using YOLOv3')
        except:
            self.yolo_model = tiny_yolo_body(Input(shape=(None, None, 3)),
                                             num_anchors // 2, num_classes) \
                if is_tiny_version else yolo_body(Input(shape=(None, None, 3)),
                                                  num_anchors // 3, num_classes)
            self.yolo_model.load_weights(
                self.model_path)  # make sure model, anchors and classes match

            print('Using tiny YOLOv3')
        else:
            assert self.yolo_model.layers[-1].output_shape[-1] == \
                   num_anchors / len(self.yolo_model.output) * (
                               num_classes + 5), \
                'Mismatch between model and given anchor and class sizes'

        print('{} model, anchors, and classes loaded.'.format(model_path))

        # Generate colors for drawing bounding boxes.
        hsv_tuples = [(x / len(self.class_names), 1., 1.)
                      for x in range(len(self.class_names))]
        self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
        self.colors = list(
                map(lambda x: (
                int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
                    self.colors))
        np.random.seed(10101)  # Fixed seed for consistent colors across runs.
        np.random.shuffle(
            self.colors)  # Shuffle colors to decorrelate adjacent classes.
        np.random.seed(None)  # Reset seed to default.

        # Generate output tensor targets for filtered bounding boxes.
        self.input_name = self.yolo_model.input
        self.input_image_shape = K.placeholder(shape=(2,), name='image_shape')
        print(self.input_image_shape)
        if self.gpu_num >= 2:
            self.yolo_model = multi_gpu_model(self.yolo_model,
                                              gpus=self.gpu_num)

        boxes, scores, classes = yolo_eval(self.yolo_model.output,
                                           self.anchors,
                                           len(self.class_names),
                                           self.input_image_shape,
                                           score_threshold=self.score,
                                           iou_threshold=self.iou)

        return boxes, scores, classes

    def detect_image(self, image):

        if self.model_image_size != (None, None):
            assert self.model_image_size[
                       0] % 32 == 0, 'Multiples of 32 required'
            assert self.model_image_size[
                       1] % 32 == 0, 'Multiples of 32 required'
            boxed_image = letterbox_image(image, tuple(
                reversed(self.model_image_size)))
        else:
            new_image_size = (image.width - (image.width % 32),
                              image.height - (image.height % 32))
            boxed_image = letterbox_image(image, new_image_size)
        image_data = np.array(boxed_image, dtype='float32')

        image_data /= 255.
        image_data = np.expand_dims(image_data, 0)  # Add batch dimension.

        out_boxes, out_scores, out_classes = self.sess.run(
                [self.boxes, self.scores, self.classes],
                feed_dict={
                        self.input_name: image_data,
                        self.input_image_shape: [image.size[1], image.size[0]],
                        K.learning_phase(): 0
                })

        return out_boxes, out_scores, out_classes

    def annotate_image(self, image, out_boxes, out_scores, out_classes):
        print('Found {} boxes for {}'.format(len(out_boxes), 'img'))

        font = ImageFont.truetype(font='font/FiraMono-Medium.otf',
                                  size=np.floor(
                                      3e-2 * image.size[1] + 0.5).astype(
                                      'int32'))
        thickness = (image.size[0] + image.size[1]) // 300

        for i, c in reversed(list(enumerate(out_classes))):
            predicted_class = self.class_names[c]
            box = out_boxes[i]
            score = out_scores[i]

            #label = '{} {:.2f}'.format(predicted_class, score)
            label = f'{predicted_class}'
            draw = ImageDraw.Draw(image)
            label_size = draw.textsize(label, font)

            top, left, bottom, right = box
            top = max(0, np.floor(top + 0.5).astype('int32'))
            left = max(0, np.floor(left + 0.5).astype('int32'))
            bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
            right = min(image.size[0], np.floor(right + 0.5).astype('int32'))
            print(label, (left, top), (right, bottom))

            if top - label_size[1] >= 0:
                text_origin = np.array([left, top - label_size[1]])
            else:
                text_origin = np.array([left, top + 1])

            # My kingdom for a good redistributable image drawing library.
            for i in range(thickness):
                draw.rectangle(
                        [left + i, top + i, right - i, bottom - i],
                        outline=self.colors[c])
            draw.rectangle(
                    [tuple(text_origin), tuple(text_origin + label_size)],
                    fill=self.colors[c])
            draw.text(text_origin, label, fill=(0, 0, 0), font=font)
            del draw

        return image

    def close_session(self):
        self.sess.close()

    def evaluate(self, validation_path, iou_thresh=0.5):

        matches = {}
        with open(os.path.abspath(validation_path), 'r') as fp:
            lines = fp.readlines()

        for line in [line.split(' ') for line in lines]:
            source, annotations = line[0], line[1:]
            n_objects = len(annotations)  # number of objects in the scene
            image = Image.open(source)
            out_boxes, out_scores, out_classes = self.detect_image(image)

            for annotation in annotations:
                data = annotation.split(',')
                bb1 = tuple(map(int, data[:4]))
                class_ = int(data[-1])

                for i in range(out_boxes.shape[0]):
                    if class_ == out_classes[i]:
                        top, left, bottom, right = out_boxes[i,:]
                        top = max(0, np.floor(top + 0.5).astype('int32'))
                        left = max(0, np.floor(left + 0.5).astype('int32'))
                        bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
                        right = min(image.size[0], np.floor(right + 0.5).astype('int32'))
                        bb2 = (left, top, right, bottom)
                        iou = _compute_iou(bb1, bb2)

                        if iou > iou_thresh: # True Positive
                            if class_ in matches:
                                matches[class_].append(iou)
                            else:
                                matches[class_] = [iou]
                        else: # False Positive
                            pass # Every image

            #  calculate the precision for each class
            for k, v in matches.items():
                tp = len(v)  # true positive
                fp = max(0, out_boxes.shape[0] - tp)  # false positive
                precision = tp/(tp + fp)  # precision

def _compute_iou(groundtruth_box, detection_box):
    g_xmin, g_ymin, g_xmax, g_ymax = groundtruth_box
    d_xmin, d_ymin, d_xmax, d_ymax = detection_box

    xa = max(g_xmin, d_xmin)
    ya = max(g_ymin, d_ymin)
    xb = min(g_xmax, d_xmax)
    yb = min(g_ymax, d_ymax)

    intersection = max(0, xb - xa + 1) * max(0, yb - ya + 1)

    boxAArea = (g_xmax - g_xmin + 1) * (g_ymax - g_ymin + 1)
    boxBArea = (d_xmax - d_xmin + 1) * (d_ymax - d_ymin + 1)

    return intersection / float(boxAArea + boxBArea - intersection)

def detect_video(yolo, video_path, output_path=""):
    vid = cv2.VideoCapture(video_path)
    if not vid.isOpened():
        raise IOError("Couldn't open webcam or video")
    video_FourCC = int(vid.get(cv2.CAP_PROP_FOURCC))
    video_fps = vid.get(cv2.CAP_PROP_FPS)
    video_size = (int(vid.get(cv2.CAP_PROP_FRAME_WIDTH)),
                  int(vid.get(cv2.CAP_PROP_FRAME_HEIGHT)))
    isOutput = True if output_path != "" else False
    if isOutput:
        print("!!! TYPE:", type(output_path), type(video_FourCC),
              type(video_fps), type(video_size))
        out = cv2.VideoWriter(output_path, video_FourCC, video_fps, video_size)
    accum_time = 0
    curr_fps = 0
    fps = "FPS: ??"
    prev_time = timer()
    while True:
        return_value, frame = vid.read()
        image = Image.fromarray(frame)
        image = yolo.annotate_image(image, *yolo.detect_image(image))
        result = np.asarray(image)
        curr_time = timer()
        exec_time = curr_time - prev_time
        prev_time = curr_time
        accum_time = accum_time + exec_time
        curr_fps = curr_fps + 1
        if accum_time > 1:
            accum_time = accum_time - 1
            fps = "FPS: " + str(curr_fps)
            curr_fps = 0
        cv2.putText(result, text=fps, org=(3, 15),
                    fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=0.50, color=(255, 0, 0), thickness=2)
        print(f'FPS: {fps}')
        cv2.namedWindow("result", cv2.WINDOW_NORMAL)
        cv2.imshow("result", result)
        if isOutput:
            out.write(result)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    yolo.close_session()


import numpy as np
import tensorflow as tf
import sys
import os

from tensorflow.compat.v1 import ConfigProto
from tensorflow.python.keras.backend import set_session

tf.keras.backend.clear_session()
config = tf.compat.v1.ConfigProto(gpu_options =
                         tf.compat.v1.GPUOptions(per_process_gpu_memory_fraction=0.9)
)
config.gpu_options.allow_growth = True
session = tf.compat.v1.Session(config=config)
tf.compat.v1.keras.backend.set_session(session)

from flask import Flask,redirect,url_for,request,render_template
from tensorflow.keras.models import model_from_json
from tensorflow.keras.preprocessing import image
from werkzeug.utils import secure_filename
from gevent.pywsgi import WSGIServer

# Define A Flask APP
app = Flask(__name__)

CATEGORIES = ['angry','disgust','fear','happy','neutral','sad','surprise']
# load model
import json
# load json and create model
json_file = open('faceexpression.json', 'r')
loaded_model_json = json_file.read()
json_file.close()
model = model_from_json(loaded_model_json)
# load weights into new model

model.load_weights("faceexpression_weights.h5")
print("Loaded model from disk")

# model = load_model("faceexpression_weights.h5")
model.summary()
model._make_predict_function()

def model_predict(img_path,model):
    img = image.load_img(img_path,color_mode="grayscale",target_size=(48,48))
    # Preprocessing the image
    x = image.img_to_array(img)
    x = np.expand_dims(x,axis=0)
    # Rescaling the image(Preprocessing the image)
    x = x/255.0
    preds = model.predict(x)
    return preds

@app.route('/',methods= ['GET'])
def index():
    # Main page
    return render_template('./index.html')

@app.route('/',methods= ['POST','GET'])
def upload():
    if request.method == "POST":
        f = request.files['file']
        # save path to './uploads'
        base_path = os.path.dirname(__file__)
        file_path = os.path.join(base_path,'uploads',secure_filename(f.filename))
        f.save(file_path)

        # Make prediction
        preds = model_predict(file_path,model)
        # Process your result for human
        pred_class = np.argmax(preds)
        result = CATEGORIES[pred_class]
        return render_template('./prediction.html',result=result)
    else:
        return render_template('./index.html')

if __name__ == '__main__':
    app.run(debug=True)

# Index.html original code
# {% comment %} <img id="bg" width=1200px height=900px src="{{ url_for('video_feed') }}"> {% endcomment %}


import numpy as np
import copy

class LayerSplitter(object):
    def __init__(self, runner, network_info, split_fc=False):
        supported_networks = ['yolov3',
                              'yolov3_416',
                              'yolov4',
                              'polylanenet_resnet_v1_34',
                              'smoke_regnetx_800mf']
        self._runner = runner
        self._hn = self._runner.get_hn()
        self._npz = self._runner.get_params()
        self._layer_names = network_info.hn_editor.output_scheme.outputs_to_split
        self._split_type = self._get_network_name()
        assert self._split_type in supported_networks, \
            'LayerSplitter does not yet support {} network architecture.'.format(self._split_type)
        if network_info.postprocessing.anchors.sizes is not None:
            self._num_anchors = len(network_info.postprocessing.anchors.sizes[0]) // 2
        self._num_classes = network_info.evaluation.classes
        self._hn_modified = None
        self._npz_modified = None
        self._num_lane_coeffs = 4
        self._max_layers = 1000

    def modify_network(self):
        self._modify_hn()
        self._runner.set_hn(self._hn_modified)
        self._modify_npz()
        self._runner.load_params(self._npz_modified)
        return self._runner

    def _modify_hn(self):
        self._hn_modified = copy.deepcopy(self._hn)
        output_index = 0
        self._remove_output_layers()
        self._clear_names_and_shapes_of_prev_layer_outputs()
        self._clear_net_params_output_layer_order()
        for layer in self._layer_names:
            if 'polylanenet' in self._split_type:
                output_layer_ordinals = list(range(0, self._max_layers))
                split_names, split_kernel_shapes = self.split_polylanenet_hn_layer(layer)
            elif 'yolo' in self._split_type:
                output_layer_ordinals = list(range(0, self._max_layers))
                split_names, split_kernel_shapes = self.split_yolov3_hn_layer(layer)
            elif 'smoke' in self._split_type:
                output_layer_ordinals = [0, 1, 3, 4, 5, 6]
                split_names, split_kernel_shapes = self.split_smoke_hn_layer(layer)
            for split_name, split_kernel_shape in zip(split_names, split_kernel_shapes):
                output_index += 1
                self._add_layer_to_hn(split_name, split_kernel_shape, layer, output_layer_ordinals[output_index])
            del(self._hn_modified["layers"][layer])

    def split_polylanenet_hn_layer(self, layer):
        kernel_shape = self._hn_modified["layers"][layer]["params"]["kernel_shape"]
        split_names = []
        split_kernel_shapes = []

        confs_name = layer + "_confs"
        confs_kernel_shape = kernel_shape[:1]
        confs_kernel_shape.append(5)
        self._hn_modified["net_params"]["output_layers_order"].append(confs_name)
        split_names.append(confs_name)
        split_kernel_shapes.append(confs_kernel_shape)

        lower_upper_name = layer + "_lower_upper"
        lower_upper_kernel_shape = kernel_shape[:1]
        lower_upper_kernel_shape.append(10)
        self._hn_modified["net_params"]["output_layers_order"].append(lower_upper_name)
        split_names.append(lower_upper_name)
        split_kernel_shapes.append(lower_upper_kernel_shape)

        coeffs_1_2_name = layer + "_coeffs_1_2"
        coeffs_1_2_kernel_shape = kernel_shape[:1]
        coeffs_1_2_kernel_shape.append(10)
        self._hn_modified["net_params"]["output_layers_order"].append(coeffs_1_2_name)
        split_names.append(coeffs_1_2_name)
        split_kernel_shapes.append(coeffs_1_2_kernel_shape)

        coeffs_3_4_name = layer + "_coeffs_3_4"
        coeffs_3_4_kernel_shape = kernel_shape[:1]
        coeffs_3_4_kernel_shape.append(10)
        self._hn_modified["net_params"]["output_layers_order"].append(coeffs_3_4_name)
        split_names.append(coeffs_3_4_name)
        split_kernel_shapes.append(coeffs_3_4_kernel_shape)
        return split_names, split_kernel_shapes

    def split_yolov3_hn_layer(self, layer):
        kernel_shape = self._hn_modified["layers"][layer]["params"]["kernel_shape"]
        split_names = []
        split_kernel_shapes = []

        centers_name = layer + "_centers"
        centers_kernel_shape = kernel_shape[:3]
        centers_kernel_shape.append(2 * self._num_anchors)
        self._hn_modified["net_params"]["output_layers_order"].append(centers_name)
        split_names.append(centers_name)
        split_kernel_shapes.append(centers_kernel_shape)

        scales_name = layer + "_scales"
        scales_kernel_shape = kernel_shape[:3]
        scales_kernel_shape.append(2 * self._num_anchors)
        self._hn_modified["net_params"]["output_layers_order"].append(scales_name)
        split_names.append(scales_name)
        split_kernel_shapes.append(scales_kernel_shape)

        obj_name = layer + "_obj"
        obj_kernel_shape = kernel_shape[:3]
        obj_kernel_shape.append(1 * self._num_anchors)
        self._hn_modified["net_params"]["output_layers_order"].append(obj_name)
        split_names.append(obj_name)
        split_kernel_shapes.append(obj_kernel_shape)

        probs_name = layer + "_probs"
        probs_kernel_shape = kernel_shape[:3]
        probs_kernel_shape.append(self._num_classes * self._num_anchors)
        self._hn_modified["net_params"]["output_layers_order"].append(probs_name)
        split_names.append(probs_name)
        split_kernel_shapes.append(probs_kernel_shape)
        return split_names, split_kernel_shapes

    def split_smoke_hn_layer(self, layer):
        kernel_shape = self._hn_modified["layers"][layer]["params"]["kernel_shape"]
        split_names = []
        split_kernel_shapes = []

        depth_name = layer + "_depth"
        depth_kernel_shape = kernel_shape[:3]
        depth_kernel_shape.append(1)
        self._hn_modified["net_params"]["output_layers_order"].append(depth_name)
        split_names.append(depth_name)
        split_kernel_shapes.append(depth_kernel_shape)

        """adding conv68 although it's not split because it needs to secure an output layer:"""
        self._hn_modified["net_params"]["output_layers_order"].append('conv68')

        offset_name = layer + "_offset"
        offset_kernel_shape = kernel_shape[:3]
        offset_kernel_shape.append(2)
        self._hn_modified["net_params"]["output_layers_order"].append(offset_name)
        split_names.append(offset_name)
        split_kernel_shapes.append(offset_kernel_shape)

        dims_name = layer + "_dims"
        dims_kernel_shape = kernel_shape[:3]
        dims_kernel_shape.append(3)
        self._hn_modified["net_params"]["output_layers_order"].append(dims_name)
        split_names.append(dims_name)
        split_kernel_shapes.append(dims_kernel_shape)

        sin_name = layer + "_sin"
        sin_kernel_shape = kernel_shape[:3]
        sin_kernel_shape.append(1)
        self._hn_modified["net_params"]["output_layers_order"].append(sin_name)
        split_names.append(sin_name)
        split_kernel_shapes.append(sin_kernel_shape)

        cos_name = layer + "_cos"
        cos_kernel_shape = kernel_shape[:3]
        cos_kernel_shape.append(1)
        self._hn_modified["net_params"]["output_layers_order"].append(cos_name)
        split_names.append(cos_name)
        split_kernel_shapes.append(cos_kernel_shape)
        return split_names, split_kernel_shapes

    def _add_layer_to_hn(self, name, shape, orig_layer, output_index):
        output_layer_name = "output_layer{}".format(output_index)
        self._hn_modified["layers"][name] = copy.deepcopy(self._hn_modified["layers"][orig_layer])
        self._hn_modified["layers"][name]["output"] = [output_layer_name]
        self._hn_modified["layers"][name]["output_shapes"][0][-1] = shape[-1]
        self._hn_modified["layers"][name]["params"]["kernel_shape"] = shape
        self._make_output_layer(name, output_layer_name, self._hn_modified["layers"][name]["output_shapes"])
        input_shape = self._hn_modified["layers"][name]["input_shapes"][0]
        self._modify_prev_layer_output_list(name, input_shape, orig_layer)

    def _modify_prev_layer_output_list(self, new_output_name, output_shape, orig_output_name):
        input_layer_name = self._hn_modified["layers"][new_output_name]["input"][0]
        self._hn_modified["layers"][input_layer_name]["output"].append(new_output_name)
        self._hn_modified["layers"][input_layer_name]["output_shapes"].append(output_shape)

    def _make_output_layer(self, input_layer_name, output_layer_name, input_shape):
        self._hn_modified["layers"][output_layer_name] = {}
        self._hn_modified["layers"][output_layer_name]["type"] = "output_layer"
        self._hn_modified["layers"][output_layer_name]["input"] = [input_layer_name]
        self._hn_modified["layers"][output_layer_name]["output"] = []
        self._hn_modified["layers"][output_layer_name]["input_shapes"] = input_shape
        self._hn_modified["layers"][output_layer_name]["output_shapes"] = input_shape
        self._hn_modified["layers"][output_layer_name]["original_names"] = ["out"]

    def _check_which_layers_to_remove(self):
        if 'smoke' in self._split_type:
            return [1]
        else:
            return None

    def _remove_output_layers(self):
        layer_list = self._check_which_layers_to_remove()
        output_layers = [layer for layer in self._hn_modified["layers"] if
                         self._hn_modified["layers"][layer]["type"] == "output_layer"]
        if layer_list:
            for layer in output_layers:
                if layer not in ['output_layer' + str(ind) for ind in layer_list]:
                    output_layers.remove(layer)

        while len(output_layers) > 0:
            output_layer = output_layers.pop()
            del(self._hn_modified["layers"][output_layer])

    def _clear_names_and_shapes_of_prev_layer_outputs(self):
        for layer in self._layer_names:
            for modified_l in self._hn_modified["layers"]:
                if layer in self._hn_modified["layers"][modified_l]["output"]:
                    self._hn_modified["layers"][modified_l]["output"] = \
                        [x for x in self._hn_modified["layers"][modified_l]["output"] if x != layer]
                    self._hn_modified["layers"][modified_l]["output_shapes"] = \
                        [x for x in self._hn_modified["layers"][modified_l]["output_shapes"] if x != layer]

    def _clear_net_params_output_layer_order(self):
        self._hn_modified["net_params"]["output_layers_order"] = []

    def _modify_npz(self):
        self._npz_modified = dict(copy.deepcopy(self._npz))
        for layer in self._layer_names:
            self._split_layer_in_npz(layer)

    def _split_layer_in_npz(self, layer):
        """replacing a layer entry in the npz with 4 layers"""
        net_name = self._get_network_name()
        layer = net_name + '/' + layer
        if 'polylanenet' in self._split_type:
            self._remodel_polylanenet_weights_and_biases_for_layer(layer)
        elif 'yolo' in self._split_type:
            self._remodel_yolov3_weights_and_biases_for_layer(layer)
        elif 'smoke' in self._split_type:
            self._remodel_smoke_weights_and_biases_for_layer(layer)

        del(self._npz_modified[layer + '/kernel:0'])
        del(self._npz_modified[layer + '/bias:0'])

    def _remodel_polylanenet_weights_and_biases_for_layer(self, layer):
        """remodeling a single output layer"""
        confs_weight_list = []
        confs_bias_list = []
        lower_upper_weight_list = []
        lower_upper_bias_list = []
        coeffs_1_2_weight_list = []
        coeffs_1_2_bias_list = []
        coeffs_3_4_weight_list = []
        coeffs_3_4_bias_list = []
        weights = copy.deepcopy(self._npz_modified[layer + '/kernel:0'])
        biases = copy.deepcopy(self._npz_modified[layer + '/bias:0'])
        for anchor in range(self._num_anchors):
            """in polylanenet #anchors==#lanes"""
            """cherry-picking the weight elements for the 4 new output layer"""
            offset = anchor * (self._num_lane_coeffs + 3)
            """the weights:"""
            confs_weight_list.append(weights[:, offset: offset + 1])
            lower_upper_weight_list.append(weights[:, offset + 1: offset + 3])
            coeffs_1_2_weight_list.append(weights[:, offset + 3: offset + 5])
            coeffs_3_4_weight_list.append(weights[:, offset + 5: offset + 7])
            """the biases:"""
            confs_bias_list.append(biases[offset: offset + 1])
            lower_upper_bias_list.append(biases[offset + 1: offset + 3])
            coeffs_1_2_bias_list.append(biases[offset + 3: offset + 5])
            coeffs_3_4_bias_list.append(biases[offset + 5: offset + 7])

        confs_weights = np.concatenate(confs_weight_list, 1)
        lower_upper_weights = np.concatenate(lower_upper_weight_list, 1)
        coeffs_1_2_weights = np.concatenate(coeffs_1_2_weight_list, 1)
        coeffs_3_4_weights = np.concatenate(coeffs_3_4_weight_list, 1)
        confs_biases = np.concatenate(confs_bias_list, 0)
        lower_upper_biases = np.concatenate(lower_upper_bias_list, 0)
        coeffs_1_2_biases = np.concatenate(coeffs_1_2_bias_list, 0)
        coeffs_3_4_biases = np.concatenate(coeffs_3_4_bias_list, 0)

        self._npz_modified[layer + '_confs/kernel:0'] = confs_weights
        self._npz_modified[layer + '_confs/bias:0'] = confs_biases

        self._npz_modified[layer + '_lower_upper/kernel:0'] = lower_upper_weights
        self._npz_modified[layer + '_lower_upper/bias:0'] = lower_upper_biases

        self._npz_modified[layer + '_coeffs_1_2/kernel:0'] = coeffs_1_2_weights
        self._npz_modified[layer + '_coeffs_1_2/bias:0'] = coeffs_1_2_biases

        self._npz_modified[layer + '_coeffs_3_4/kernel:0'] = coeffs_3_4_weights
        self._npz_modified[layer + '_coeffs_3_4/bias:0'] = coeffs_3_4_biases

    def _remodel_yolov3_weights_and_biases_for_layer(self, layer):
        """remodeling a single output layer"""
        centers_weight_list = []
        centers_bias_list = []
        scales_weight_list = []
        scales_bias_list = []
        obj_weight_list = []
        obj_bias_list = []
        probs_weight_list = []
        probs_bias_list = []
        weights = copy.deepcopy(self._npz_modified[layer + '/kernel:0'])
        biases = copy.deepcopy(self._npz_modified[layer + '/bias:0'])
        for anchor in range(self._num_anchors):
            """cherry-picking the weight elements for the 4 new output layer"""
            offset = anchor * (self._num_classes + 5)
            """the weights:"""
            centers_weight_list.append(weights[:, :, :, offset: offset + 2])
            scales_weight_list.append(weights[:, :, :, offset + 2: offset + 4])
            obj_weight_list.append(weights[:, :, :, offset + 4: offset + 5])
            probs_weight_list.append(weights[:, :, :, offset + 5: offset + 5 + self._num_classes])
            """the biases:"""
            centers_bias_list.append(biases[offset: offset + 2])
            scales_bias_list.append(biases[offset + 2: offset + 4])
            obj_bias_list.append(biases[offset + 4: offset + 5])
            probs_bias_list.append(biases[offset + 5: offset + 5 + self._num_classes])

        centers_weights = np.concatenate(centers_weight_list, 3)
        scales_weights = np.concatenate(scales_weight_list, 3)
        obj_weights = np.concatenate(obj_weight_list, 3)
        probs_weights = np.concatenate(probs_weight_list, 3)
        centers_biases = np.concatenate(centers_bias_list, 0)
        scales_biases = np.concatenate(scales_bias_list, 0)
        obj_biases = np.concatenate(obj_bias_list, 0)
        probs_biases = np.concatenate(probs_bias_list, 0)

        self._npz_modified[layer + '_centers/kernel:0'] = centers_weights
        self._npz_modified[layer + '_centers/bias:0'] = centers_biases

        self._npz_modified[layer + '_scales/kernel:0'] = scales_weights
        self._npz_modified[layer + '_scales/bias:0'] = scales_biases

        self._npz_modified[layer + '_obj/kernel:0'] = obj_weights
        self._npz_modified[layer + '_obj/bias:0'] = obj_biases

        self._npz_modified[layer + '_probs/kernel:0'] = probs_weights
        self._npz_modified[layer + '_probs/bias:0'] = probs_biases

    def _remodel_smoke_weights_and_biases_for_layer(self, layer, rescale=1.0):
        """remodeling a single output layer"""
        weights = copy.deepcopy(self._npz_modified[layer + '/kernel:0'])
        biases = copy.deepcopy(self._npz_modified[layer + '/bias:0'])
        """the weights:"""
        depth_weights = weights[:, :, :, :1]
        offset_weights = weights[:, :, :, 1:3]
        dims_weights = weights[:, :, :, 3:6]
        sin_weights = weights[:, :, :, 6:7]
        cos_weights = weights[:, :, :, 7:] * rescale
        """the biases:"""
        depth_biases = biases[:1]
        offset_biases = biases[1:3]
        dims_biases = biases[3:6]
        sin_biases = biases[6:7]
        cos_biases = biases[7:] * rescale

        self._npz_modified[layer + '_depth/kernel:0'] = depth_weights
        self._npz_modified[layer + '_depth/bias:0'] = depth_biases

        self._npz_modified[layer + '_offset/kernel:0'] = offset_weights
        self._npz_modified[layer + '_offset/bias:0'] = offset_biases

        self._npz_modified[layer + '_dims/kernel:0'] = dims_weights
        self._npz_modified[layer + '_dims/bias:0'] = dims_biases

        self._npz_modified[layer + '_sin/kernel:0'] = sin_weights
        self._npz_modified[layer + '_sin/bias:0'] = sin_biases

        self._npz_modified[layer + '_cos/kernel:0'] = cos_weights
        self._npz_modified[layer + '_cos/bias:0'] = cos_biases

    def _get_network_name(self):
        hn_dict = self._hn
        return hn_dict['name']