from matplotlib import pyplot as plt
import numpy as np
import cv2
from object_detection.builders import model_builder
from object_detection.utils import visualization_utils as viz_utils
from object_detection.utils import label_map_util
from google.protobuf import text_format
from object_detection.protos import pipeline_pb2
from object_detection.utils import config_util
import tensorflow as tf
import os
from pathlib import Path
import wget

# restart the environment after installing if this fails to import
import object_detection

# Basic setup
CUSTOM_MODEL_NAME = 'my_ssd_mobnet'
PRETRAINED_MODEL_NAME = 'ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8'
PRETRAINED_MODEL_URL = 'http://download.tensorflow.org/models/object_detection/tf2/20200711/ssd_mobilenet_v2_fpnlite_320x320_coco17_tpu-8.tar.gz'
TF_RECORD_SCRIPT_NAME = 'generate_tfrecord.py'
LABEL_MAP_NAME = 'label_map.pbtext'


paths = {
    # the workspace folder
    'WORKSPACE_PATH': os.path.join('Tensorflow', 'workspace'),
    'SCRIPTS_PATH': os.path.join('Tensorflow', 'scripts'),
    'APIMODEL_PATH': os.path.join('Tensorflow', 'models'),
    # the TF record files
    'ANNOTATION_PATH': os.path.join('Tensorflow', 'workspace', 'annotations'),
    'IMAGE_PATH': os.path.join('Tensorflow', 'workspace', 'images'),
    'MODEL_PATH': os.path.join('Tensorflow', 'workspace', 'models'),
    'PRETRAINED_MODEL_PATH': os.path.join('Tensorflow', 'workspace', 'pre-trained-models'),
    'CHECKPOINT_PATH': os.path.join('Tensorflow', 'workspace', 'models', CUSTOM_MODEL_NAME),
    'OUTPUT_PATH': os.path.join('Tensorflow', 'workspace', 'models', CUSTOM_MODEL_NAME, 'export'),
    'TFJS_PATH': os.path.join('Tensorflow', 'workspace', 'models', CUSTOM_MODEL_NAME, 'tfjsexport'),
    'TFLITE_PATH': os.path.join('Tensorflow', 'workspace', 'models', CUSTOM_MODEL_NAME, 'tfliteexport'),
    # for the protocol buffers
    'PROTOC_PATH': os.path.join('Tensorflow', 'protoc')
}

files = {
    'PIPELINE_CONFIG': os.path.join('Tensorflow', 'workspace', 'models', CUSTOM_MODEL_NAME, 'pipeline.config'),
    'TF_RECORD_SCRIPT': os.path.join(paths['SCRIPTS_PATH'], TF_RECORD_SCRIPT_NAME),
    'LABELMAP': os.path.join(paths['ANNOTATION_PATH'], LABEL_MAP_NAME),
}

for path in paths.values():
    Path(path).mkdir(parents=True, exist_ok=True)


# download the pretrained models etc
if not (Path(paths['APIMODEL_PATH']) / 'research' / 'object').exists():
    os.system(
        f"git clone https://github.com/tensorflow/models {paths['APIMODEL_PATH']}")

# install Tensorflow Object Detection
if os.name == 'posix':
    os.system("apt-get install protobuf-computer")
    os.system(
        "cd Tensorflow/models/research && protoc object_detection/protos/*.proto --python_out=. && cp object_detection/packages/tf2/setup.py setup.py && python setup.py build && python setup.py install")

if os.name == 'nt':
    url = 'https://github.com/protocolbuffers/protobuf/releases/download/v3.15.6/protoc-3.15.6-win64.zip'
    wget.download(url)
    os.system(f"move protoc-3.15.6-win64.zip {paths['PROTOC_PATH']}")
    os.system(f"cd {paths['PROTOC_PATH']} && tar -xf protoc-3.15.6-win64.zip")
    os.environ['PATH'] += os.pathsep + \
        os.path.abspath(os.path.join(paths['PROTOC_PATH'], 'bin'))
    os.system("cd Tensorflow/models/research && protoc object_detection/protos/*.proto --python_out=. && copy object_detection\\packages\\tf2\\setup.py setup.py && python setup.py build && python setup.py install")
    os.system("cd Tensorflow/models/research/slim && pip install -e .")

# keep running and re-installing packages until this returns "OK (skipped=n)"
VERIFICATION_SCRIPT = os.path.join(
    paths['APIMODEL_PATH'], 'research', 'object_detection', 'builders', 'model_builder_tf2_test.py')
os.system(f"python {VERIFICATION_SCRIPT}")


if os.name == 'posix':
    os.system(f"wget {PRETRAINED_MODEL_URL}")
    os.system(
        f"mv {PRETRAINED_MODEL_NAME}.tar.gz {paths['PRETRAINED_MODEL_PATH']}")
    os.system(
        f"cd {paths['PRETRAINED_MODEL_PATH']} && tar -zxvf {PRETRAINED_MODEL_NAME}.tar.gz")
if os.name == 'nt':
    wget.download(PRETRAINED_MODEL_URL)
    os.system(
        f"move {PRETRAINED_MODEL_NAME}.tar.gz {paths['PRETRAINED_MODEL_PATH']}")
    os.system(
        f"cd {paths['PRETRAINED_MODEL_PATH']} && tar -zxvf {PRETRAINED_MODEL_NAME}.tar.gz")


# create Label Map
labels = [{'name': 'ThumbsUp', 'id': 1},
          {'name': 'ThumbsDown', 'id': 2},
          {'name': 'ThankYou', 'id': 3},
          {'name': 'LiveLong', 'id': 4}]

with open(files['LABELMAP'], 'w') as f:
    for label in labels:
        f.write('item { \n')
        f.write('\tname:\'{}\'\n'.format(label['name']))
        f.write('\tid:{}\n'.format(label['id']))
        f.write('}\n')


# Create TF records
# Binary file format for storing data; helps speed up training for the custom object detection model
if not os.path.exists(files['TF_RECORD_SCRIPT']):
    os.system(
        f"git clone https://github.com/nicknochnack/GenerateTFRecord {paths['SCRIPTS_PATH']}")

os.system(
    f"python {files['TF_RECORD_SCRIPT']} -x {os.path.join(paths['IMAGE_PATH'], 'train')} -l {files['LABELMAP']} -o {os.path.join(paths['ANNOTATION_PATH'], 'train.record')}")
os.system(
    f"python {files['TF_RECORD_SCRIPT']} -x {os.path.join(paths['IMAGE_PATH'], 'test')} -l {files['LABELMAP']} -o {os.path.join(paths['ANNOTATION_PATH'], 'test.record')}")


# Copy Model Config to Training Folder
if os.name == 'posix':
    os.system(
        f"cp {os.path.join(paths['PRETRAINED_MODEL_PATH'], PRETRAINED_MODEL_NAME, 'pipeline.config')} {os.path.join(paths['CHECKPOINT_PATH'], 'pipeline.config')}")
if os.name == 'nt':
    os.system(
        f"copy {os.path.join(paths['PRETRAINED_MODEL_PATH'], PRETRAINED_MODEL_NAME, 'pipeline.config')} {os.path.join(paths['CHECKPOINT_PATH'], 'pipeline.config')}")


# Update Config for Transfer Learning
# import tensorflow as tf
# from object_detection.utils import config_util
# from object_detection.protos import pipeline_pb2
# from google.protobuf import text_format
config = config_util.get_configs_from_pipeline_file(files['PIPELINE_CONFIG'])
pipeline_config = pipeline_pb2.TrainEvalPipelineConfig()
with tf.io.gfile.GFile(files['PIPELINE_CONFIG'], "r") as f:
    proto_str = f.read()
    text_format.Merge(proto_str, pipeline_config)

pipeline_config.model.ssd.num_classes = len(labels)
pipeline_config.train_config.batch_size = 4
pipeline_config.train_config.fine_tune_checkpoint = os.path.join(
    paths['PRETRAINED_MODEL_PATH'], PRETRAINED_MODEL_NAME, 'checkpoint', 'ckpt-0')
pipeline_config.train_config.fine_tune_checkpoint_type = "detection"
pipeline_config.train_input_reader.label_map_path = files['LABELMAP']
pipeline_config.train_input_reader.tf_record_input_reader.input_path[:] = [
    os.path.join(paths['ANNOTATION_PATH'], 'train.record')]
pipeline_config.eval_input_reader[0].label_map_path = files['LABELMAP']
pipeline_config.eval_input_reader[0].tf_record_input_reader.input_path[:] = [
    os.path.join(paths['ANNOTATION_PATH'], 'test.record')]

config_text = text_format.MessageToString(pipeline_config)
with tf.io.gfile.GFile(files['PIPELINE_CONFIG'], "wb") as f:
    f.write(config_text)

# Train the model
TRAINING_SCRIPT = os.path.join(
    paths['APIMODEL_PATH'], 'research', 'object_detection', 'model_main_tf2.py')
command = "python {} --model_dir={} --pipeline_config_path={} --num_train_steps=2000".format(
    TRAINING_SCRIPT, paths['CHECKPOINT_PATH'], files['PIPELINE_CONFIG'])
print(command)
os.system(command)


# Evaluate the model
command = "python {} --model_dir={} --pipeline_config_path={} --checkpoint_dir={}".format(
    TRAINING_SCRIPT, paths['CHECKPOINT_PATH'], files['PIPELINE_CONFIG'], paths['CHECKPOINT_PATH'])
print(command)
os.system(command)


# Load Train Model from Checkpoint
# import os
# import tensorflow as tf
# from object_detection.utils import label_map_util
# from object_detection.utils import visualization_utils as viz_utils
# from object_detection.builders import model_builder
# from object_detection.utils import config_util

# Load pipeline config and build a detection model
configs = config_util.get_configs_from_pipeline_file(files['PIPELINE_CONFIG'])
detection_model = model_builder.build(
    model_config=configs['model'], is_training=False)

# Restore checkpoint
ckpt = tf.compat.v2.train.Checkpoint(model=detection_model)
ckpt.restore(os.path.join(paths['CHECKPOINT_PATH'], 'ckpt-3')).expect_partial()


@tf.function
def detect_fn(image):
    image, shapes = detection_model.preprocess(image)
    prediction_dict = detection_model.predict(image, shapes)
    detections = detection_model.postprocess(prediction_dict, shapes)
    return detections


# Detecting from an image
# import cv2
# import numpy as np
# from matplotlib import pyplot as plt

category_index = label_map_util.create_category_index_from_labelmap(
    files['LABELMAP'])
IMAGE_PATH = os.path.join(
    paths['IMAGE_PATH'], 'test', 'livelong.e7200124-251b-11ed-9477-24ee9ab7760d.jpg')

img = cv2.imread(IMAGE_PATH)
image_np = np.array(img)

input_tensor = tf.convert_to_tensor(
    np.expand_dims(image_np, 0), dtype=tf.float32)
detections = detect_fn(input_tensor)

num_detections = int(detections.pop('num_detections'))
detections = {key: value[0, :num_detections].numpy()
              for key, value in detections.items()}

detections['num_detections'] = num_detections


# detection_classes should be ints
detections['detection_classes'] = detections['detection_classes'].astype(
    np.int64)


label_id_offset = 1
image_np_with_detections = image_np.copy()

viz_utils.visualize_boxes_and_labels_on_image_array(image_np_with_detections,
                                                    detections['detection_boxes'],
                                                    detections['detection_classes'] +
                                                    label_id_offset,
                                                    detections['detection_scores'],
                                                    category_index,
                                                    use_normalized_coordinates=True,
                                                    max_boxes_to_draw=5,
                                                    min_score_thresh=0.8,
                                                    agnostic_mode=False)

plt.imshow(cv2.cvtColor(image_np_with_detections, cv2.COLOR_BGR2RGB))
# plt.show()
# plt.savefig('test.png')


# Real Time Detections from Webcam
cap = cv2.VideoCapture(0)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

while cap.isOpened():
    ret, frame = cap.read()
    image_np = np.array(frame)

    input_tensor = tf.convert_to_tensor(
        np.expand_dims(image_np, 0), dtype=tf.float32)
    detections = detect_fn(input_tensor)

    num_detections = int(detections.pop('num_detections'))
    detections = {key: value[0, :num_detections].numpy()
                  for key, value in detections.items()}

    detections['num_detections'] = num_detections

    # detection_classes should be ints
    detections['detection_classes'] = detections['detection_classes'].astype(
        np.int64)

    label_id_offset = 1
    image_np_with_detections = image_np.copy()

    viz_utils.visualize_boxes_and_labels_on_image_array(image_np_with_detections,
                                                        detections['detection_boxes'],
                                                        detections['detection_classes'] +
                                                        label_id_offset,
                                                        detections['detection_scores'],
                                                        category_index,
                                                        use_normalized_coordinates=True,
                                                        max_boxes_to_draw=5,
                                                        min_score_thresh=0.8,
                                                        agnostic_mode=False)

    cv2.imshow('object_detection', cv2.resize(
        image_np_with_detections, (800, 600)))

    if cv2.waitKey(10) & 0xFF == ord('q'):
        cap.release()
        cv2.destroyAllWindows()
        break


# Freezing and Conversion
# Freezing is saving a slice of the model, so it can be used next time
FREEZE_SCRIPT = os.path.join(
    paths['APIMODEL_PATH'], 'research', 'object_detection', 'exporter_main_v2.py')

command = f"python {FREEZE_SCRIPT} --input_type=image_tensor --pipeline_config_path={files['PIPELINE_CONFIG']} --trained_checkpoint_dir={paths['CHECKPOINT_PATH']} --output_directory={paths['OUTPUT_PATH']}"
print(command)
os.system(command)


# Conversion to TFJS (so we can embed into a website)
# may require `pip install tensorflowjs`
command = f"tensorflowjs_converter --input_format=tf_saved_model --output_node_names='detection_boxes,detection_classes,detection_features,detection_multiclass_scores,detection_scores,num_detections,raw_detection_boxes,raw_detection_scores' --output_format=tfjs_graph_model --signature_name=serving_default {os.path.join(paths['OUTPUT_PATH'], 'saved_model')} {paths['TFJS_PATH']}"
print(command)
# os.system(command)


# Conversion to TFLite (so we can embed to a Raspberry Pi)
TFLITE_SCRIPT = os.path.join(
    paths['APIMODEL_PATH'], 'research', 'object_detection', 'export_tflite_graph_tf2.py ')
command = f"python {TFLITE_SCRIPT} --pipeline_config_path={files['PIPELINE_CONFIG']} --trained_checkpoint_dir={paths['CHECKPOINT_PATH']} --output_directory={paths['TFLITE_PATH']}"

print(command)
os.system(command)


FROZEN_TFLITE_PATH = os.path.join(paths['TFLITE_PATH'], 'saved_model')
TFLITE_MODEL = os.path.join(
    paths['TFLITE_PATH'], 'saved_model', 'detect.tflite')


command = f"tflite_convert \
--saved_model_dir={FROZEN_TFLITE_PATH} \
--output_file={TFLITE_MODEL} \
--input_shapes=1,300,300,3 \
--input_arrays=normalized_input_image_tensor \
--output_arrays='TFLite_Detection_PostProcess','TFLite_Detection_PostProcess:1','TFLite_Detection_PostProcess:2','TFLite_Detection_PostProcess:3' \
--inference_type=FLOAT \
--allow_custom_ops"

print(command)
os.system(command)


# then Zip and export the models
os.system(f"tar -czf models.tar.gz {paths['CHECKPOINT_PATH']}")
