#!/usr/bin/env python
# coding: utf-8

#
#
# # Earth Analytics Education - EA  Python Course Spring 2021

# ## Important  - Assignment Guidelines
#
# 1. Before you submit your assignment to GitHub, make sure to run the entire notebook with a fresh kernel. To do this first, **restart the kernel** (in the menubar, select Kernel$\rightarrow$Restart & Run All)
# 2. Always replace the `raise NotImplementedError()` code with your code that addresses the activity challenge. If you don't replace that code, your notebook will not run.
#
# ```
# # YOUR CODE HERE
# raise NotImplementedError()
# ```
#
# 3. Any open ended questions will have a "YOUR ANSWER HERE" within a markdown cell. Replace that text with your answer also formatted using Markdown.
# 4. **DO NOT RENAME THIS NOTEBOOK File!** If the file name changes, the autograder will not grade your assignment properly.
# 6. When you create a figure, comment out `plt.show()` to ensure the autograder can grade your plots. For figure cells, DO NOT DELETE the code that says `DO NOT REMOVE LINE BELOW`.
#
# ```
# ### DO NOT REMOVE LINE BELOW ###
# student_plot1_ax = nb.convert_axes(plt)
# ```
#
# * Only include the package imports, code, and outputs that are required to run your homework assignment.
# * Be sure that your code can be run on any operating system. This means that:
#    1. the data should be downloaded in the notebook to ensure it's reproducible
#    2. all paths should be created dynamically using the `os.path.join`
#
# ## Follow to PEP 8 Syntax Guidelines & Documentation
#
# * Run the `autopep8` tool on all cells prior to submitting (HINT: hit shift + the tool to run it on all cells at once!
# * Use clear and expressive names for variables.
# * Organize your code to support readability.
# * Check for code line length
# * Use comments and white space sparingly where it is needed
# * Make sure all python imports are at the top of your notebook and follow PEP 8 order conventions
# * Spell check your Notebook before submitting it.
#
# For all of the plots below, be sure to do the following:
#
# * Make sure each plot has a clear TITLE and, where appropriate, label the x and y axes. Be sure to include UNITS in your labels.
#

# ### Add Your Name Below
# **Your Name:** Maeve McCormick

#

# ---

# # Week 04 and 05 Homework - Automate NDVI Workflow
#
# For this assignment, you will write code to generate a plot of the mean normalized difference vegetation index (NDVI) for two different sites in the United States across one year of data:
#
# * San Joaquin Experimental Range (SJER) in Southern California, United States
# * Harvard Forest (HARV) in the Northeastern United States
#
# The data that you will use for this week is available from **earthpy** using the following download:
#
# `et.data.get_data('ndvi-automation')`
#
# ## Assignment Goals
#
# Your goal in this assignment is to create the most efficient and concise workflow that you can that allows for:
#
# 1. The code to scale if you added new sites or more time periods to the analysis.
# 2. Someone else to understand your workflow.
# 3. The LEAST and most efficient (i.e. runs fast, minimize repetition) amount of code that completes the task.
#
# ### HINTS
#
# * Remove values outside of the landsat valid range of values as specified in the metadata, as needed.
# * Keep any output files SEPARATE FROM input files. Outputs should be created in an outputs directory that is created in the code (if needed) and/or tested for.
# * Use the functions that we demonstrated during class to make your workflow more efficient.
# * BONUS - if you  chose - you can export your data as a csv file. You will get bonus points for doing this.
#
#
# ## Assignment Requirements
#
# Your submission to the GitHub repository should include:
# * This Jupyter Notebook file (.ipynb) with:
#     * The code to create a plot of mean NDVI across a year for  2 NEON Field Sites:
#         * NDVI on the x axis and formatted dates on the y for both NEON sites on one figure/axis object
#     * The **data should be cleaned to remove the influence of clouds**. See the [earthdatascience website for an example of what your plot might look like with and without removal of clouds](https://www.earthdatascience.org/courses/earth-analytics-python/create-efficient-data-workflows/).
# * BONUS: Create one output `.csv` file that has 3 columns - NDVI, Date and Site Name - with values for SJER and HARV.
#
# Your notebook should:
# * Have *at least* 2 well documented and well named functions with docstrings.
# * Include a Markdown cell at the top of the notebook that outlines the overall workflow using pseudocode (i.e. plain language, not code)
# * Include additional Markdown cells throughout the notebook to describe:
#     * the data that you used - and where it is from
#     * how data are being processing
#     * how the code is optimized to run fast and be more concise

# # Replace this cell with your pseudocode  for this workflow
#
# If you happen to be a diagram person a diagram is ok too
#
#

# ## Maeve's workflow:
# * Import needed packages, set the working directory, and download the data with earthpy.
# * Define a variable for the values used for cloud masking right away because you'll need it throughout the script and it's nice to have at the top of your workflow so it gets called right away.
# * Pick a single scene from a single site and use it to figure out what you need to do to grab and then process the needed files (clipping, masking, etc).
# * Write functions to automate the step-by-step workflow you come up with (probably one to get the files and a separate one to crop, mask, and calculate ndvi). **Make sure you replace any specific variables/parameter names with generic ones so the functions work universally, and not just on a given part of the data.**
# * Test your functions on the single site again to make sure they work and return a reasonable/expected result.
# * Calculate mean ndvi from the cropped and masked ndvi.
# * Pull the date and site name from the file name/directory path and store those in a list with the mean ndvi values.
# * Convert the list to a dataframe and reset the index to the date column.
# * Write a series of nested loops using your functions to automate through *all* of the data in the directories.
#     * The outermost loop should cycle through sites (make it generic enough - we only have two in this case but it could be adapted to loop through more sites if the data were available).
#     * The crop boundary and pixel qa files should probably be selected in this outer loop, so that those steps aren't repeated for each scene and only change from site to site.
#     * Nest a second loop within the outer loop to loop through each scene file within the sites directories. This loop should apply the functions you wrote, calculate mean ndvi, pull scene date from the file or directory name, and then compile site name, date, and mean ndvi in a list.
#     * You may need more than one inner loop, but I think just the two nested ones are enough.
# * Outside the loops, convert the list of ndvi and other attributes to a dataframe, as you did for the single site above. Remember to format the date and reset the index to the date column.
# * Plot mean ndvi vs date using the dataframe object you generated with the loop!

# In[1]:

# Autograding imports - do not modify this cell
import matplotcheck.autograde as ag
import matplotcheck.notebook as nb
import matplotcheck.timeseries as ts
from datetime import datetime

# In[2]:

# Import needed packages in PEP 8 order
# and no unused imports listed (10 points total)
import os
from glob import glob

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.dates import DateFormatter
from matplotlib.axes._axes import _log as matplotlib_axes_logger

import numpy as np
import pandas as pd
import rasterio as rio
import rioxarray as rxr
import xarray as xr

import earthpy as et
import geopandas as gpd
import seaborn as sns
import warnings

# Prettier plotting with seaborn
sns.set_style('white')
sns.set(font_scale=1.5)
# Suppress a warning about nan values in the ndvi calculation
warnings.simplefilter('ignore')

# Download the necessary data using earthpy
et.data.get_data('ndvi-automation')

# Define a variable for the working directory path.
wd_path = os.path.join(et.io.HOME,
                       "earth-analytics",
                       "data")
# Set the working directory or make the directory if it does not already exist.
if os.path.exists(wd_path):
    os.chdir(wd_path)
    print("The current working directory is", wd_path)
else:
    os.makedirs(wd_path)
    os.chdir(wd_path)
    print("The path does not exist but is being created")

# In[3]:

# DO NOT MODIFY THIS CELL
# Tests that the working directory is set to earth-analytics/data

path = os.path.normpath(os.getcwd())
student_wd_parts = path.split(os.sep)

if student_wd_parts[-2:] == ['earth-analytics', 'data']:
    print("\u2705 Great - it looks like your working directory is set correctly to ~/earth-analytics/data")
else:
    print("\u274C Oops, the autograder will not run unless your working directory is set to earth-analytics/data")

# # Figure 1: Plot 1 - Mean NDVI For Each Site Across the Year (50 points)
#
# Create a plot of the mean normalized difference vegetation index (NDVI) for the two different sites in the United States across the year:
#
# * NDVI on the x axis and formatted dates on the y for both NEON sites on one figure/axis object.
# * Each site should be identified with a different color in the plot and legend.
# * The final plot **data should be cleaned to remove the influence of clouds**.
# * Be sure to include appropriate title and axes labels.
#
# Add additional cells as needed for processing data (e.g. defining functions, etc), but be sure to:
# * follow the instructions in the code cells that have been provided to ensure that you are able to use the sanity check tests that are provided.
# * include only the plot code in the cell identified for the final plot code below

# ## Task 1:
#
# In the cell below, create a single dataframe containing MEAN NDVI, the site name,
# and the date of the data for the HARV site
# scene `HARV/landsat-crop/LC080130302017031701T1-SC20181023151837`.  The column names for the  final
# DataFrame should be`mean_ndvi`, and `site`, and the data should be **indexed on the date**.
#
# Use the functions that we reviewed in class (or create your own versions of them) to implement your code
#
# ### In the Cell below Place  All Functions Needed to Run this Notebook (20 points)

# In[4]:

### DO NOT REMOVE THIS LINE OR EDIT / MOVE THIS CELL ###
start_time = datetime.now()

# In[5]:

# In this cell place all of the functions needed to run your notebook
# You will be graded here on function application, docstrings, efficiency so ensure
# All functions are placed here!

def get_bands(band_directory, valid_range=None):
    """Get a sorted list of paths to the files containing landsat bands 4 and 5
    and open the files as xarray objects.

    Parameters
    ----------
    band_directory : string
        A string describing the file directory containing the landsat bands.

    valid_range : tuple (optional)
        A tuple containing the valid range (min and max) of expected values
        (default input is none).

    Returns
    ----------
    all_bands : list
        A list of two xarray DataArray objects, one for each landsat band.
    """
    # Get a list of bands 4 and 5
    band_paths = sorted(glob(os.path.join(band_directory,
                                          "*band*[4-5].tif")))

    # Open the bands, mask for valid values, and append them to a list
    all_bands = []
    for path in band_paths:
        a_band = rxr.open_rasterio(path, masked=True).squeeze()
        if valid_range is not None:
            mask = ((a_band < valid_range[0]) | (a_band > valid_range[1]))
            a_band = a_band.where(~xr.where(mask, True, False))

        all_bands.append(a_band)

    return all_bands

def mask_crop_ndvi(all_bands,
                   crop_bound,
                   pixel_qa,
                   vals):
    """Calculate normalized difference vegetation index (NDVI) from the
    provided landsat bands. Clip the output NDVI layer and the given
    pixel qa layer to the boundary specified by the crop_bound file. Use the
    cropped pixel qa layer and vals to apply a cloud mask to the cropped NDVI.
    Save the cropped and masked NDVI layer as an xarray.

    Parameters
    -----------
    all_bands : list
        A list containing the xarray objects for landsat  bands 4 and  5.

    crop_bound: geopandas GeoDataFrame
        A geopandas dataframe to be used to crop the raster data using
        rasterio mask().

    pixel_qa: xarray DataArray
        An xarray DataArray with pixel qa values that have not yet been turned
        into a mask (0s and 1s).

    vals: list
        A list of values needed to create the cloud mask.

    Returns
    -----------
    ndvi_crop : xarray DataArray
        A cropped and masked xarray object containing NDVI values.
    """

    crop_json = crop_bound.geometry

    # Clip pixel qa cloud mask layer
    cl_mask_crop = pixel_qa.rio.clip(crop_json)

    # Calculate NDVI
    ndvi_xr = (all_bands[1]-all_bands[0]) / (all_bands[1]+all_bands[0])
    # Clip NDVI layer
    ndvi_crop = ndvi_xr.rio.clip(crop_json)

    # Apply cloud mask to NDVI
    ndvi_crop = ndvi_crop.where(~cl_mask_crop.isin(vals))

    return ndvi_crop

# ##### Reminders from instructor comments
# ` Important: to use the ungraded tests below as a sanity check, name your columns: mean_ndvi and site
#  Call the dataframe at the end of the cell so the tests run on it!
#  Be sure that the date column is an index of type date
#  HINT: the time series lessons may help you remember how to do this!`
#
# ### Preliminary Setup
# The following cell contains code that defines variables for the directories that will be looped through later in the workflow. The code also defines a list of vaules used for cloud masking.

# In[6]:

# Define site paths
sites_path = os.path.join("ndvi-automation", "sites")
all_sites = sorted(glob(os.path.join(sites_path, '*/')))
# Name a variable for the folder containing landsat files
landsat_dir = "landsat-crop"

# List the cloud no data vals for Landsat 8:
vals = [328, 392, 840, 904, 1350, 352, 368, 416,
        432, 480, 864, 880, 928, 944, 992, 480, 992]
# Specify a valid range of landsat values:
valid_range = (0, 10000)

# #### Single HARV scene setup
# The following cell defines the directory for the specific HARV scene we've been asked to analyze. It also pulls the site name and the date from the directory and navigates to and opens the qa layer and crop layer.

# In[7]:

# File path to single scene
harv_single_path = os.path.join(sites_path,
                                "HARV",
                                "landsat-crop",
                                "LC080130302017031701T1-SC20181023151837")
# Get site name and date from directory name
dir_name = os.path.basename(os.path.normpath(harv_single_path))
date = dir_name[10:18]
site_name = os.path.basename(os.path.normpath(all_sites[0]))

# Open scene qa and site boundary files
harv_single_qa_path = glob(os.path.join(harv_single_path,
                                        "*qa*"))
qa_layer = rxr.open_rasterio(harv_single_qa_path[0], masked=True).squeeze()
boundary_path = os.path.join(sites_path,
                             site_name,
                             "vector",
                             site_name + "-crop.shp")
boundary = gpd.read_file(boundary_path)

# #### Single site workflow

# In[8]:

# Create dataframe of mean NDVI in this cell using the functions created above
# Get bands then crop, mask, and calculate ndvi
all_bands = get_bands(harv_single_path, valid_range)
ndvi_value = mask_crop_ndvi(all_bands,
                            boundary,
                            qa_layer,
                            vals)
# Calculate mean ndvi
ndvi_mean = np.nanmean(ndvi_value)

# Capture the mean ndvi, site name, and date in a list
ndvi_list = []
ndvi_list.append([site_name, date, ndvi_mean])

# Convert the NDVI list generated above to a dataframe and rename the columns.
harv_single_mean_ndvi = pd.DataFrame(ndvi_list,
                                     columns=["site", "date", "mean_ndvi"])
harv_single_mean_ndvi['date'] = pd.to_datetime(harv_single_mean_ndvi['date'],
                                               format='%Y%m%d')
harv_single_mean_ndvi = harv_single_mean_ndvi.set_index(['date'])
harv_single_mean_ndvi

# In[9]:

# This cell  is testing your data output above

student_ndvi_ts_single_site = _

single_scene_points = 0

# Ensure the data is stored in a dataframe.
if isinstance(student_ndvi_ts_single_site, pd.DataFrame):
    print('\u2705 Your data is stored in a DataFrame!')
    single_scene_points += 1
else:
    print('\u274C It appears your data is not stored in a DataFrame. ',
          'To see what type of object your data is stored in, check its type with type(object)')

# Ensure that the date column is the index
if isinstance(student_ndvi_ts_single_site.index, pd.core.indexes.datetimes.DatetimeIndex):
    print('\u2705 You have the index set to the date column!')
    single_scene_points += 2
else:
    print('\u274C You do not have the index set to the date column.')

# Ensure that the date column is datetime
if isinstance(student_ndvi_ts_single_site.index[0], pd._libs.tslibs.timestamps.Timestamp):
    print('\u2705 The data in your date column is datetime!')
    single_scene_points += 2
else:
    print('\u274C The data in your date column is not datetime.')

# Ensure the site name is correct
if student_ndvi_ts_single_site.site.values[0] == 'HARV':
    print('\u2705 You have the correct site name!')
    single_scene_points += 5
else:
    print('\u274C You do not have the correct site name.')

if np.allclose(0.281131628228094, student_ndvi_ts_single_site.mean_ndvi.values[0]):
    print('\u2705 You have the correct mean NDVI value!')
    single_scene_points += 5
else:
    print('\u274C You do not have the correct mean ndvi value.')

print("\n \u27A1 You received {} out of 15 points for creating a dataframe.".format(
    single_scene_points))
single_scene_points

# ## Task 2:
#
# In the cell below, process all of the landsat scenes. Create a DataFrame that contains the following
# information for each scene
#
#
# |   | index  | site  | mean_ndvi  |
# |---|---|---|---|
# | Date  |   |   |   |
# |  2017-01-07  | 0  | SJER  | .4  |
#
# Be sure to call your dataframe at the end of the cell to ensure autograding works.
# HINT: FOR THIS STEP, leave any rows containing missing values (`NAN`).

# In[10]:

# Loop through all directories and apply the functions you wrote.
''' This loop contains optional 'print' statements to help track progress.
They are flagged with '# OPTIONAL PRINT' at the end of the line.
Uncomment the ones you wish to use.'''

# Create an empty list for storing ndvi values.
ndvi_list = []

# Loop through each site directory.
for site_files in all_sites:
    #print("I am looping through", site_files) # OPTIONAL PRINT
    a_site = os.path.split(os.path.normpath(site_files))[1]
    print("I am working on the", a_site, "field site now.")

    # Get the boundary shapefile for clipping from the vector directory.
    vector_dir = os.path.join(site_files, "vector")
    boundary_path = os.path.join(vector_dir,  a_site + "-crop.shp")
    boundary = gpd.read_file(boundary_path)
    #print("The current boundary path is", boundary_path) # OPTIONAL PRINT

    # Get a list of subdirectories for the current site.
    new_path = os.path.join(site_files, landsat_dir)
    all_dirs = sorted(glob(os.path.join(new_path, "*/")))

    #  Loop through  each subdirectory where your data are stored.
    for a_dir in all_dirs:
        #print("Now processing", a_dir) # OPTIONAL PRINT
        # Get the date from the directory name
        dir_name = os.path.basename(os.path.normpath(a_dir))
        date = dir_name[10:18]

        # Get cloud mask layer (qa file)
        qa_path = glob(os.path.join(a_dir, "*qa*"))
        qa_layer = rxr.open_rasterio(qa_path[0], masked=True).squeeze()

        # Get landsat bands 4 and 5, crop and mask them, and calculate ndvi
        all_bands = get_bands(a_dir, valid_range)
        ndvi_value = mask_crop_ndvi(all_bands,
                                    boundary,
                                    qa_layer,
                                    vals)
        # Calculate mean NDVI
        ndvi_mean = np.nanmean(ndvi_value)

        # Capture  the site name, and  date in  a list
        ndvi_list.append([a_site, date, ndvi_mean])

print("Processing complete.")

# In[11]:

# Create dataframe of NDVI including the cleaning data to deal with clouds
# Convert the NDVI list generated above to a dataframe and rename the columns.
mean_ndvi = pd.DataFrame(ndvi_list,
                         columns=["site", "date", "mean_ndvi"])
mean_ndvi['date'] = pd.to_datetime(mean_ndvi['date'], format='%Y%m%d')
mean_ndvi.reset_index(inplace=True)
mean_ndvi.set_index(['date'], inplace=True)
mean_ndvi

# In[12]:

# Last sanity check before creating your plot (10 points)

# Ensure that you call your dataframe at the bottom of the cell above
# and that it has columns called: mean_ndvi and site

# Ensure the data is stored in a dataframe.
student_ndvi_df = _

df_points = 0

if isinstance(student_ndvi_df, pd.DataFrame):
    print('\u2705 Your data is stored in a DataFrame!')
    df_points += 2
else:
    print('\u274C It appears your data is not stored in a DataFrame. ',
          'To see what type of object your data is stored in, check its type with type(object)')

# Check that dataframe contains the appropriate number of NAN values
if student_ndvi_df.mean_ndvi.isna().sum() == 15:
    print('\u2705 Correct number of masked data values!')
    df_points += 2
else:
    print('\u274C The amount of null data in your dataframe is incorrect.')

# Ensure that the date column is the index
if isinstance(student_ndvi_df.index, pd.core.indexes.datetimes.DatetimeIndex):
    print('\u2705 You have the index set to the date column!')
    df_points += 3
else:
    print('\u274C You do not have the index set to the date column.')

# Ensure that the date column is datetime
if isinstance(student_ndvi_df.index[0], pd._libs.tslibs.timestamps.Timestamp):
    print('\u2705 The data in your date column is datetime!')
    df_points += 3
else:
    print('\u274C The data in your date column is not datetime.')

# Output for timer, # DO NOT MODIFY
end_time = datetime.now()
total_time = end_time - start_time
print(
    "Your total run time for processing the data was {0}.".format(total_time))

print("\n \u27A1 You received {} out of 10 points for creating a dataframe.".format(
    df_points))

df_points

# In[13]:

# Plot mean NDVI for both sites across the year.
fig1, ax1 = plt.subplots(figsize=(14, 10))

# Group the data by site
ndvi_site_gr = mean_ndvi.dropna().groupby('site')

for site, group in ndvi_site_gr:
    ax1.plot(group.index.values,
             group['mean_ndvi'].values,
             label=site,
             marker='o')

# Format plot axes and title
ax1.set(xlabel='Date',
        ylabel='Mean NDVI Value',
        title='Mean Normalized Difference Vegetation Index (NDVI)\n for the HARV and SJER NEON sites, 2017')
ax1.legend()

# Define date format
date_form = DateFormatter("%b-%d")
ax1.xaxis.set_major_formatter(date_form)

### DO NOT REMOVE LINES BELOW ###
final_masked_solution = nb.convert_axes(plt, which_axes="current")

# In[14]:

# Ignore this cell for the autograding tests

# In[15]:

# Ignore this cell for the autograding tests

# # Question 1 (10 points)
#
# Imagine that you are planning NEON’s upcoming flight season to capture remote sensing data in these locations and want to ensure that you fly the area when the vegetation is the most green.
#
# When would you recommend the flights take place for each site?
#
# Answer the question in 2-3 sentences in the Markdown cell below.

# The San Joaquin Experimental Range is located near Fresno, CA, an area with a Mediterranean climate. The open oak woodland typically reaches peak greenness in early March. Harvard Forest (near Boston, MA) is a northern hardwood and coniferous forest in a relatively cool and temperate climate. However, warming temperatures have resulted in an earlier onset of spring weather patterns, and average peak greenness at this location is now also reached by early March. In order to ideally capture peak greenness conditions, NEON flights for these sites would occur the first or second week of March.

# # Question 2 (10 points)
#
# How could you modify your workflow to look at vegetation changes over time in each site?
#
# Answer the question in 2-3 sentences in the Markdown cell below.

# In order to look at changes in vegetation over time, I'd first obtain landsat data from an additional year. I'd process the data the same way those from 2017 were in this workflow, and then additionally calculate a difference in mean ndvi from year to year. The difference operation could probably be added to the end of the multi-site processing loop.

# # Do not edit this cell! (10 points)
#
# The notebook includes:
# * additional Markdown cells throughout the notebook to describe:
#     * the data that you used - and where it is from
#     * how data are being processing
#     * how the code is optimized to run fast and be more concise

# # Do not edit this cell! (20 points)
#
# The notebook will also be checked for overall clean code requirements as specified at the **top** of this notebook. Some of these requirements include (review the top cells for more specifics):
#
# * Notebook begins at cell [1] and runs on any machine in its entirety.
# * PEP 8 format is applied throughout (including lengths of comment and code lines).
# * No additional code or imports in the notebook that is not needed for the workflow.
# * Notebook is fully reproducible. This means:
#    * reproducible paths using the os module.
#    * data downloaded using code in the notebook.
#    * all imports at top of notebook.

# ## BONUS - Export a  .CSV File to Share (10 points possible)
#
# This is optional - if you export a **.csv** file with the columns specified above: Site, Date and NDVI Value you can get an additional 10 points.
#
# * FULL CREDIT: File exists in csv format and contains the columns specified.
# We will check your github repo for this file!
#


import numpy as np
import pyautogui
import win32api, win32con, win32gui
import cv2
import math
import time

CONFIG_FILE = './yolov3.cfg'
WEIGHT_FILE = './yolov3.weights'

net = cv2.dnn.readNetFromDarknet(CONFIG_FILE, WEIGHT_FILE)
#net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)

ln = net.getLayerNames()
ln = [ln[i[0] - 1] for i in net.getUnconnectedOutLayers()]

# Get rect of Window
hwnd = win32gui.FindWindow(None, 'Counter-Strike: Global Offensive')
rect = win32gui.GetWindowRect(hwnd)
region = rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]

size_scale = 2
while True:
    # Get image of screen
    frame = np.array(pyautogui.screenshot(region=region))
    frame_height, frame_width = frame.shape[:2]

    # Detection
    blob = cv2.dnn.blobFromImage(frame, 1 / 255.0, (416, 416), swapRB=True, crop=False)
    net.setInput(blob)
    layerOutputs = net.forward(ln)

    boxes = []
    confidences = []

    for output in layerOutputs:
        for detection in output:
            scores = detection[5:]
            classID = np.argmax(scores)
            confidence = scores[classID]
            if confidence > 0.7 and classID == 0:
                box = detection[:4] * np.array([frame_width, frame_height, frame_width, frame_height])
                (centerX, centerY, width, height) = box.astype("int")
                x = int(centerX - (width / 2))
                y = int(centerY - (height / 2))
                box = [x, y, int(width), int(height)]
                boxes.append(box)
                confidences.append(float(confidence))

    indices = cv2.dnn.NMSBoxes(boxes, confidences, 0.7, 0.6)

    # Calculate distance for picking the closest enemy from crosshair
    if len(indices) > 0:
        print(f"Detected:{len(indices)}")
        min = 99999
        min_at = 0
        for i in indices.flatten():
            (x, y) = (boxes[i][0], boxes[i][1])
            (w, h) = (boxes[i][2], boxes[i][3])
            cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 255, 255), 2)

            dist = math.sqrt(math.pow(frame_width/2 - (x+w/2), 2) + math.pow(frame_height/2 - (y+h/2), 2))
            if dist < min:
                min = dist
                min_at = i

        # Distance of the closest from crosshair
        x = int(boxes[min_at][0] + boxes[min_at][2]/2 - frame_width/2)
        y = int(boxes[min_at][1] + boxes[min_at][3]/2 - frame_height/2) - boxes[min_at][3] * 0.5 # For head shot

        # Move mouse and shoot
        scale = 1.7
        x = int(x * scale)
        y = int(y * scale)
        win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, x, y, 0, 0)
        time.sleep(0.05)
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
        time.sleep(0.1)
        win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)

    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame = cv2.resize(frame, (frame.shape[1] // size_scale, frame.shape[0] // size_scale))
    cv2.imshow("frame", frame)
    cv2.waitKey(1)


import asyncio
import concurrent
import os
from concurrent.futures import ThreadPoolExecutor
from unittest import TestCase

import cv2
import matplotlib.pyplot as plt
import numpy as np
from skimage.measure import compare_ssim

from bot.providers import Nox
from bot.utils.common import default_config
from bot.duel_links_runtime import DuelLinkRunTime
from bot.common import mask_image, mse

def compare_images(image_a, image_b, title):
    # compute the mean squared error and structural similarity
    # index for the images
    m = mse(image_a, image_b)
    s = compare_ssim(image_a, image_b, multichannel=True)

    # setup the figure
    fig = plt.figure(title)
    plt.suptitle("MSE: %.2f, SSIM: %.2f" % (m, s))

    # show first image
    ax = fig.add_subplot(1, 2, 1)
    plt.imshow(image_a, cmap=plt.cm.gray)
    plt.axis("off")

    # show the second image
    ax = fig.add_subplot(1, 2, 2)
    plt.imshow(image_b, cmap=plt.cm.gray)
    plt.axis("off")

    # show the images
    plt.show()

class TestNox(TestCase):
    provider = None
    __debug_pictures__ = False

    images_needed_debug = [
        "street_replay.png"
    ]

    def setUp(self):
        os.environ['LOG_CFG'] = r'D:\Sync\OneDrive\Yu-gi-oh_bot\config.ini'
        dlRuntime = DuelLinkRunTime(default_config(r'D:\Sync\OneDrive\Yu-gi-oh_bot'), None, False)
        self.provider = Nox(None, default_config(r'D:\Sync\OneDrive\Yu-gi-oh_bot'), dlRuntime)
        self.provider.sleep_factor = 0.0
        loop = asyncio.get_event_loop()
        loop.set_default_executor(ThreadPoolExecutor(2))

    def test_provider(self):
        with self.assertRaises(AssertionError) as context:
            provider = Nox(None, default_config(r'D:\Sync\OneDrive\Fake'), None)
        self.assertTrue('Missing File' in str(context.exception))

    def test_start_process(self):
        with self.assertRaises(FileNotFoundError) as context:
            self.provider.NoxPath = 'C:\\Nox\\Not\\Here'
            self.provider.start_process()

    def test_initial_pass_through(self):
        test_function = lambda x: x is False
        self.provider.__start_app__()
        with self.assertRaises(Exception) as context:
            self.provider.__generic_wait_for__('DuelLinks Landing Page', test_function,
                                               None)
        self.assertTrue('Maximum exception count' in str(context.exception))
        self.provider.sleep_factor = 0.5
        with self.assertRaises(concurrent.futures._base.TimeoutError) as context:
            self.provider.__generic_wait_for__('DuelLinks Landing Page', test_function,
                                               self.provider.__is_initial_screen__, timeout=5)

    def test_initial_pass_through_compare(self):
        original = cv2.imread(os.path.join(self.provider.assets, "start_screen.png"))
        against = self.provider.get_img_from_screen_shot()
        wrong = cv2.imread(os.path.join(self.provider.assets, "battle.png"))

        # convert the images to grayscale
        original = mask_image([127], [255], cv2.cvtColor(original, cv2.COLOR_BGR2GRAY), True)
        against = mask_image([127], [255], cv2.cvtColor(against, cv2.COLOR_BGR2GRAY), True)
        wrong = mask_image([127], [255], cv2.cvtColor(wrong, cv2.COLOR_BGR2GRAY), True)
        # initialize the figure
        (score, diff) = compare_ssim(original, against, full=True)
        diff = (diff * 255).astype("uint8")
        self.assertTrue(score > .90, 'If this is less then .90 the initial compare of the app will fail')
        (score, nothing) = compare_ssim(original, wrong, full=True)
        self.assertTrue(score < .90)
        if self.__debug_pictures__:
            # threshold the difference image, followed by finding contours to
            # obtain the regions of the two input images that differ
            thresh = cv2.threshold(diff, 0, 255,
                                   cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
            cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
                                    cv2.CHAIN_APPROX_SIMPLE)
            cnts = cnts[0]
            # loop over the contours
            for c in cnts:
                # compute the bounding box of the contour and then draw the
                # bounding box on both input images to represent where the two
                # images differ
                (x, y, w, h) = cv2.boundingRect(c)
                cv2.rectangle(original, (x, y), (x + w, y + h), (0, 0, 255), 2)
                cv2.rectangle(against, (x, y), (x + w, y + h), (0, 0, 255), 2)
            # show the output images
            diffs = ("Original", original), ("Modified", against), ("Diff", diff), ("Thresh", thresh)
            images = ("Original", original), ("Against", against), ("Wrong", wrong)
            self.setup_compare_images(diffs)
            self.setup_compare_images(images)

    def test_is_process_running(self):
        self.fail()

    def test_kill_process(self):
        self.fail()

    def test_ok_box_comparsion(self):
        test_image_name = "street_replay.png"
        self.provider._debug = False
        self.provider.run_time.stop = False
        test_image = os.path.join(self.provider.assets, test_image_name)
        ok_present = self.provider.scan_for_ok(img=cv2.imread(test_image))
        self.assertTrue(ok_present)

    def setup_compare_images(self, images, compare_against_first=False):
        i = np.asarray([isinstance(x, tuple) for x in list(images)])
        assert i.all()
        fig = plt.figure("Images")
        # loop over the images
        for (i, (name, image)) in enumerate(images):
            # show the image
            ax = fig.add_subplot(2, 2, i + 1)
            ax.set_title(name)
            plt.imshow(image, cmap=plt.cm.gray)
            plt.axis("off")

        # show the figure
        plt.show()
        if not compare_against_first:
            return
        original = images[0]
        for (i, (name, image)) in enumerate(images[1:]):
            # compare the images
            compare_images(original[1], image, "Original vs. {}".format(name))


import subprocess

from handcam.ltt.util.TFTools import (
    _DatasetInitializerHook,
    shuffle_dataset,
    per_sequence_standardization,
    per_sequence_standardization_rgbd,
)

import tensorflow as tf
import glob
import sys
import numpy as np
import six
import os
import pickle
import datetime

# from handcam.ltt.network.model.Wide_ResNet import wide_resnet_tf_depth as resnet_model_rgbd
from handcam.ltt.network.model.Wide_ResNet import wide_resnet_tf as resnet_model
from handcam.ltt.network.model.RNNModel import LSTMModel as lstm_model
from handcam.ltt.util.Utils import AttrDict, handcam_gesture_spotting_acc

models_to_eval = {
    "/media/luke/hdd-3tb/models/handcam/split0/sequence_resnet-18/rgbd-imu/train/2018-09-05/23:33": None,
    "/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-18/rgbd-imu/train/2018-09-06/02:41": None,
    "/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-18/rgbd-imu/train/2018-09-06/05:56": None,
    "/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-18/rgbd-imu/train/2018-09-06/08:51": None,
    "/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-18/rgbd-imu/train/2018-09-06/11:48": None,
    "/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-18/rgbd-imu/train/2018-09-06/16:19": None,
    "/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-18/rgbd-imu/train/2018-09-06/18:59": None,
    "/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-18/rgbd-imu/train/2018-09-06/21:20": None,
    "/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-18/rgbd-imu/train/2018-09-07/00:59": None,
    "/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-18/rgbd-imu/train/2018-09-07/03:36": None
    # '/media/luke/hdd-3tb/models/handcam/split0/sequence_resnet-18/depth/frozen_train/2018-08-20/19:40': None,
    # '/media/luke/hdd-3tb/models/handcam/split0/sequence_resnet-18/depth/train/2018-08-20/22:25': None,
    # '/media/luke/hdd-3tb/models/handcam/split0/sequence_resnet-18/rgbd/frozen_train/2018-08-20/21:32': None,
    # '/media/luke/hdd-3tb/models/handcam/split0/sequence_resnet-18/rgbd/train/2018-08-21/00:01': None,
    # '/media/luke/hdd-3tb/models/handcam/split0/sequence_resnet-18/rgb/frozen_train/2018-08-20/20:22': None,
    # '/media/luke/hdd-3tb/models/handcam/split0/sequence_resnet-18/rgb/train/2018-08-20/23:09': None,
    # '/media/luke/hdd-3tb/models/handcam/split0/single_frames_resnet-18/depth/train/2018-08-20/18:22': None,
    # '/media/luke/hdd-3tb/models/handcam/split0/single_frames_resnet-18/rgbd/train/2018-08-20/19:08': None,
    # '/media/luke/hdd-3tb/models/handcam/split0/single_frames_resnet-18/rgb/train/2018-08-20/18:46': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-18/depth/frozen_train/2018-08-21/21:19': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-18/depth/train/2018-08-21/23:38': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-18/rgbd/frozen_train/2018-08-21/22:40': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-18/rgbd/train/2018-08-22/01:17': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-18/rgb/frozen_train/2018-08-21/21:57': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-18/rgb/train/2018-08-22/00:17': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-50/depth/frozen_train/2018-08-23/19:12': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-50/rgbd/frozen_train/2018-08-23/23:04': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/sequence_resnet-50/rgb/frozen_train/2018-08-23/20:16': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/single_frames_resnet-18/depth/train/2018-08-21/17:55': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/single_frames_resnet-18/rgbd/train/2018-08-21/18:44': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/single_frames_resnet-18/rgb/train/2018-08-21/18:14': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/single_frames_resnet-50/depth/train/2018-08-23/11:51': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/single_frames_resnet-50/rgbd/train/2018-08-23/13:36': None,
    # '/media/luke/hdd-3tb/models/handcam/split1/single_frames_resnet-50/rgb/train/2018-08-23/12:32': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-18/depth/frozen_train/2018-08-22/04:11': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-18/depth/train/2018-08-22/08:31': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-18/rgbd/frozen_train/2018-08-22/06:55': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-18/rgbd/train/2018-08-22/09:59': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-18/rgb/frozen_train/2018-08-22/04:55': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-18/rgb/train/2018-08-22/08:55': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-50/depth/frozen_train/2018-08-24/04:35': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-50/rgbd/frozen_train/2018-08-24/07:02': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/sequence_resnet-50/rgb/frozen_train/2018-08-24/05:26': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/single_frames_resnet-18/depth/train/2018-08-22/02:17': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/single_frames_resnet-18/rgbd/train/2018-08-22/03:35': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/single_frames_resnet-18/rgb/train/2018-08-22/02:57': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/single_frames_resnet-50/depth/train/2018-08-24/02:08': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/single_frames_resnet-50/rgbd/train/2018-08-24/03:37': None,
    # '/media/luke/hdd-3tb/models/handcam/split2/single_frames_resnet-50/rgb/train/2018-08-24/02:42': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-18/depth/frozen_train/2018-08-22/12:23': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-18/depth/train/2018-08-22/15:58': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-18/rgbd/frozen_train/2018-08-22/15:08': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-18/rgbd/train/2018-08-22/18:08': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-18/rgb/frozen_train/2018-08-22/13:59': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-18/rgb/train/2018-08-22/17:17': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-50/depth/frozen_train/2018-08-24/12:50': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-50/rgbd/frozen_train/2018-08-24/16:53': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/sequence_resnet-50/rgb/frozen_train/2018-08-24/14:32': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/single_frames_resnet-18/depth/train/2018-08-22/11:00': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/single_frames_resnet-18/rgbd/train/2018-08-22/11:48': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/single_frames_resnet-18/rgb/train/2018-08-22/11:16': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/single_frames_resnet-50/depth/train/2018-08-24/09:24': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/single_frames_resnet-50/rgbd/train/2018-08-24/11:23': None,
    # '/media/luke/hdd-3tb/models/handcam/split3/single_frames_resnet-50/rgb/train/2018-08-24/10:10': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-18/depth/frozen_train/2018-08-22/20:36': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-18/depth/train/2018-08-22/23:04': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-18/rgbd/frozen_train/2018-08-22/22:14': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-18/rgbd/train/2018-08-23/01:16': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-18/rgb/frozen_train/2018-08-22/21:36': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-18/rgb/train/2018-08-23/00:10': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-50/depth/frozen_train/2018-08-24/23:00': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-50/rgbd/frozen_train/2018-08-25/04:29': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/sequence_resnet-50/rgb/frozen_train/2018-08-25/01:49': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/single_frames_resnet-18/depth/train/2018-08-22/19:11': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/single_frames_resnet-18/rgbd/train/2018-08-22/19:55': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/single_frames_resnet-18/rgb/train/2018-08-22/19:30': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/single_frames_resnet-50/depth/train/2018-08-24/19:55': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/single_frames_resnet-50/rgbd/train/2018-08-24/21:43': None,
    # '/media/luke/hdd-3tb/models/handcam/split4/single_frames_resnet-50/rgb/train/2018-08-24/20:50': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-18/depth/frozen_train/2018-08-23/04:06': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-18/depth/train/2018-08-23/06:34': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-18/rgbd/frozen_train/2018-08-23/05:55': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-18/rgbd/train/2018-08-23/08:19': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-18/rgb/frozen_train/2018-08-23/05:13': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-18/rgb/train/2018-08-23/07:38': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-50/depth/frozen_train/2018-08-25/11:27': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-50/rgbd/frozen_train/2018-08-25/15:06': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/sequence_resnet-50/rgb/frozen_train/2018-08-25/13:11': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/single_frames_resnet-18/depth/train/2018-08-23/02:54': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/single_frames_resnet-18/rgbd/train/2018-08-23/03:39': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/single_frames_resnet-18/rgb/train/2018-08-23/03:22': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/single_frames_resnet-50/depth/train/2018-08-25/07:09': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/single_frames_resnet-50/rgbd/train/2018-08-25/09:37': None,
    # '/media/luke/hdd-3tb/models/handcam/split5/single_frames_resnet-50/rgb/train/2018-08-25/08:35': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-18/depth/frozen_train/2018-08-23/12:15': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-18/depth/train/2018-08-23/15:13': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-18/rgbd/frozen_train/2018-08-23/13:58': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-18/rgbd/train/2018-08-23/17:53': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-18/rgb/frozen_train/2018-08-23/12:50': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-18/rgb/train/2018-08-23/16:47': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-50/depth/frozen_train/2018-08-26/00:18': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-50/rgbd/frozen_train/2018-08-26/03:24': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/sequence_resnet-50/rgb/frozen_train/2018-08-26/01:13': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/single_frames_resnet-18/depth/train/2018-08-23/10:39': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/single_frames_resnet-18/rgbd/train/2018-08-23/11:38': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/single_frames_resnet-18/rgb/train/2018-08-23/11:06': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/single_frames_resnet-50/depth/train/2018-08-25/19:42': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/single_frames_resnet-50/rgbd/train/2018-08-25/21:27': None,
    # '/media/luke/hdd-3tb/models/handcam/split6/single_frames_resnet-50/rgb/train/2018-08-25/20:25': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-18/depth/frozen_train/2018-08-22/16:30': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-18/depth/train/2018-08-22/21:24': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-18/rgb/frozen_train/2018-08-22/18:23': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-18/rgb/train/2018-08-22/22:36': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-18/rgbd/frozen_train/2018-08-28/10:04': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-18/rgbd/train/2018-08-28/10:38': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-50/depth/frozen_train/2018-08-26/11:24': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-50/rgbd/frozen_train/2018-08-26/14:05': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/sequence_resnet-50/rgb/frozen_train/2018-08-26/12:13': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/single_frames_resnet-18/depth/train/2018-08-22/14:30': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/single_frames_resnet-18/rgb/train/2018-08-22/14:57': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/single_frames_resnet-18/rgbd/train/2018-08-28/09:21': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/single_frames_resnet-50/depth/train/2018-08-26/07:39': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/single_frames_resnet-50/rgbd/train/2018-08-26/09:30': None,
    # '/media/luke/hdd-3tb/models/handcam/split7/single_frames_resnet-50/rgb/train/2018-08-26/08:36': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-18/depth/frozen_train/2018-08-23/00:54': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-18/depth/train/2018-08-23/03:20': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-18/rgbd/frozen_train/2018-08-23/02:27': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-18/rgbd/train/2018-08-23/04:51': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-18/rgb/frozen_train/2018-08-23/01:27': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-18/rgb/train/2018-08-23/04:08': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-50/depth/frozen_train/2018-08-26/22:18': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-50/rgbd/frozen_train/2018-08-27/02:58': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/sequence_resnet-50/rgb/frozen_train/2018-08-26/23:59': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/single_frames_resnet-18/depth/train/2018-08-22/23:20': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/single_frames_resnet-18/rgbd/train/2018-08-23/00:30': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/single_frames_resnet-18/rgb/train/2018-08-22/23:55': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/single_frames_resnet-50/depth/train/2018-08-26/19:11': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/single_frames_resnet-50/rgbd/train/2018-08-26/21:03': None,
    # '/media/luke/hdd-3tb/models/handcam/split8/single_frames_resnet-50/rgb/train/2018-08-26/20:06': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-18/depth/frozen_train/2018-08-23/07:21': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-18/depth/train/2018-08-23/09:15': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-18/rgbd/frozen_train/2018-08-23/08:35': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-18/rgbd/train/2018-08-23/10:38': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-18/rgb/frozen_train/2018-08-23/07:58': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-18/rgb/train/2018-08-23/09:49': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-50/depth/frozen_train/2018-08-27/07:32': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-50/rgbd/frozen_train/2018-08-27/12:23': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/sequence_resnet-50/rgb/frozen_train/2018-08-27/09:29': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/single_frames_resnet-18/depth/train/2018-08-23/05:40': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/single_frames_resnet-18/rgbd/train/2018-08-23/06:58': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/single_frames_resnet-18/rgb/train/2018-08-23/06:25': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/single_frames_resnet-50/depth/train/2018-08-27/04:49': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/single_frames_resnet-50/rgbd/train/2018-08-27/06:13': None,
    # '/media/luke/hdd-3tb/models/handcam/split9/single_frames_resnet-50/rgb/train/2018-08-27/05:31': None
}

# dict[resnet_size][seq_or_single][modality][gesture_or_accuracy]
compiled_results_dict = {
    "resnet-50": {"single_frames": {}, "sequence_frozen": {}},
    "resnet-18": {"single_frames": {}, "sequence_frozen": {}, "sequence_end2end": {}},
}

for resnet_type in compiled_results_dict.keys():
    for model_type in compiled_results_dict[resnet_type].keys():
        for split_id in range(0, 10):
            compiled_results_dict[resnet_type][model_type]["split%d" % split_id] = {}
            if split_id == 0 and resnet_type == "resnet-50":
                if model_type == "single_frames":
                    compiled_results_dict[resnet_type][model_type][
                        "split%d" % split_id
                    ]["rgb"] = {"accuracy": 0.9818, "gesture_spotting": 0.9945}
                    compiled_results_dict[resnet_type][model_type][
                        "split%d" % split_id
                    ]["depth"] = {"accuracy": 0.8604, "gesture_spotting": 0.9394}
                    compiled_results_dict[resnet_type][model_type][
                        "split%d" % split_id
                    ]["rgbd"] = {"accuracy": 0.9813, "gesture_spotting": 0.9924}
                else:
                    compiled_results_dict[resnet_type][model_type][
                        "split%d" % split_id
                    ]["rgb"] = {"accuracy": 0.9540, "gesture_spotting": 0.9922}
                    compiled_results_dict[resnet_type][model_type][
                        "split%d" % split_id
                    ]["depth"] = {"accuracy": 0.9461, "gesture_spotting": 0.9864}
                    compiled_results_dict[resnet_type][model_type][
                        "split%d" % split_id
                    ]["rgbd"] = {"accuracy": 0.9602, "gesture_spotting": 0.9919}
                # need to put the numbers from the paper in here, models are gone.
                pass
            else:
                for modality in ["depth", "rgb", "rgbd"]:
                    compiled_results_dict[resnet_type][model_type][
                        "split%d" % split_id
                    ][modality] = {"accuracy": None, "gesture_spotting": None}

# run for each model. Load the FLAGS.pckl file to set everything up the same way as for training
for model_path in models_to_eval.keys():
    checkpoint_id = models_to_eval[model_path]

    if (
        "/tmp/luke/handcam" not in model_path
        and "/media/luke/hdd-3tb" not in model_path
    ):
        model_path = os.path.join("/tmp/luke/handcam/", model_path)

    with open(os.path.join(model_path, "FLAGS.pckl"), "rb") as f:
        flags_dict = pickle.load(f)
        FLAGS = AttrDict(flags_dict)  # allow attribute access to FLAGS.

    # need to modify FLAGS a bit
    FLAGS.batch_size = 1

    # Sanity check FLAGS
    if FLAGS.input_modality not in ["rgb", "rgbd", "depth"]:
        raise (ValueError("input_modality must be one of: rgb, rgbd, depth."))
    else:
        print(FLAGS.input_modality)

    if FLAGS.model_type not in ["single_frames", "sequence"]:
        raise (ValueError("model_type must be one of: single_frames, sequence."))

    if FLAGS.mode not in ["train", "eval", "frozen_train"]:
        raise (ValueError("mode must be one of: train, eval, frozen_train"))

    if FLAGS.resnet_size not in [18, 50]:
        raise (ValueError("resnet size must be one of: 18, 50"))

    with open(os.path.join(model_path, "results.pckl"), "rb") as f:
        out_dict = pickle.load(f)

    print(
        "per frame: %.2f\tgesture spotting: %.2f"
        % (100 * out_dict["val_accuracy"], 100 * out_dict["gesture_spotting_accuracy"])
    )

    dict_resnet_type = "resnet-%d" % FLAGS.resnet_size
    dict_model_type = FLAGS.model_type
    if FLAGS.model_type != "single_frames":
        dict_model_type = (
            "sequence_frozen" if FLAGS.mode == "frozen_train" else "sequence_end2end"
        )

    dict_split_name = "split%d" % FLAGS.validation_split_num
    dict_modality = FLAGS.input_modality

    compiled_results_dict[dict_resnet_type][dict_model_type][dict_split_name][
        dict_modality
    ] = {
        "accuracy": out_dict["val_accuracy"],
        "gesture_spotting": out_dict["gesture_spotting_accuracy"],
    }

with open("/home/luke/github/master-thesis/python/all_validations_imu.pckl", "wb") as f:
    pickle.dump(compiled_results_dict, f)


import cv2
from cv_bridge import CvBridge
import VisionExtensions
import numpy as np
from .candidate import Candidate, CandidateFinder
import itertools
import random
import rospy
from .live_fcnn_03 import FCNN03

class FcnnHandler(CandidateFinder):
    """
    The :class:`.FcnnHandler` handles Fully Convolutional Neural Networks, meaning it finds and rates candidates in their output.
    The FCNN handler runs the FCNN and manages its predictions.
    """

    def __init__(self, config, fcnn):
        """
        Initialization of :class:`.FcnnHandler`.

        :param dict config: dictionary of the vision node configuration parameters
        :param FCNN03 fcnn: a fcnn model
        """
        self._image = None
        self._fcnn = fcnn
        self._rated_candidates = None
        self._sorted_rated_candidates = None
        self._top_candidate = None
        self._fcnn_output = None
        self._cv_bridge = CvBridge()

        # init config
        self.set_config(config)

    def set_image(self, image):
        """
        Set a image for the fcnn. This also resets the caches.

        :param image: current vision image
        :return: None
        """
        self._image = image
        self._rated_candidates = None
        self._sorted_rated_candidates = None
        self._top_candidate = None
        self._fcnn_output = None

    def set_config(self, config):
        """
        Set all configuration parameters for the fcnn.

        :param dict config: dictionary of the vision node configuration parameters
        :return: None
        """
        self._debug = config['ball_fcnn_publish_debug_img']
        self._threshold = config['ball_fcnn_threshold']
        self._expand_stepsize = config['ball_fcnn_expand_stepsize']
        self._pointcloud_stepsize = config['ball_fcnn_pointcloud_stepsize']
        self._min_candidate_diameter = config['ball_fcnn_min_ball_diameter']
        self._max_candidate_diameter = config['ball_fcnn_max_ball_diameter']
        self._candidate_refinement_iteration_count = config['ball_fcnn_candidate_refinement_iteration_count']

    def get_candidates(self):
        """
        Returns all ball candidates. This method is cached.

        :return: all ball candidates
        """
        # Check if a cached value exists
        if self._rated_candidates is None:
            # Create candidate list
            self._rated_candidates = list()
            # Run neural network and clustering and iterate over the given candidates
            for candidate in self._get_raw_candidates_cpp():
                # Get the fcnn heatmap
                out = self.get_fcnn_output()
                # Calculate the mean in the ROI in the heatmap
                rating = np.mean(
                    out[
                        candidate.get_upper_left_y():
                        candidate.get_upper_left_y() + candidate.get_height(),
                        candidate.get_upper_left_x():
                        candidate.get_upper_left_x() + candidate.get_width()]
                ) / 255.0
                candidate.set_rating(rating)
                # Check if candidate is in rating threshold and size bounds
                if self._inspect_candidate(candidate):
                    # Add candidate to list
                    self._rated_candidates.append(candidate)
        return self._rated_candidates

    def _inspect_candidate(self, candidate):
        """
        Checks if candidates is in threshold. And in min/max diameter bounds.

        :param candidate: a Ball candidate
        :return: a boolean if the candidate satisfies these conditions
        """
        # type: (Candidate) -> bool
        return candidate.get_rating() >= self._threshold \
               and self._min_candidate_diameter \
               <= candidate.get_diameter() \
               <= self._max_candidate_diameter

    def compute(self):
        """
        Runs the neural network.
        """
        # Call get candidates and drop the returned solution because it get cached for the real call
        self.get_candidates()

    def get_fcnn_output(self):
        """
        Calculates the fcnn heatmap. The output gets cached.

        :return: fcnn output
        """
        # Check if a cached one exists
        if self._fcnn_output is None:
            # Resize image for fcnn
            in_img = cv2.resize(self._image, (self._fcnn._input_shape[1], self._fcnn._input_shape[0]))
            # Convert image to floats
            in_img = cv2.cvtColor(in_img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
            # Predict
            out = self._fcnn.predict(list([in_img]))
            # Reshape fcnn output
            out = out.reshape(self._fcnn._output_shape[0], self._fcnn._output_shape[1])
            # Convert back to uint8 dtype
            out = (out * 255).astype(np.uint8)
            # Resize the heatmap to match the resolution of the in comming image
            self._fcnn_output = cv2.resize(out, (self._image.shape[1], self._image.shape[0]))
        return self._fcnn_output

    def _get_raw_candidates_cpp(self):
        """
        Runs the fcnn heatmap clustering candidate detection.

        :return: ball candidates
        """
        start = cv2.getTickCount()
        # Get fcnn output
        out = self.get_fcnn_output()
        end = cv2.getTickCount()
        rospy.logdebug(str((end - start) / cv2.getTickFrequency()), logger_name="vision_fcnn_handler")
        start = cv2.getTickCount()
        # Mask the heatmap with a threshold
        r, out_bin = cv2.threshold(out, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        # Run cpp vision extention to find clusters in the heatmap
        tuple_candidates = VisionExtensions.findSpots(out_bin, self._pointcloud_stepsize, self._expand_stepsize, self._candidate_refinement_iteration_count)
        candidates = list()
        rospy.logdebug(str(len(tuple_candidates)), logger_name="vision_fcnn_handler")
        # Convert output tuples to candidates
        for candidate in tuple_candidates:
            # Calculate final width and height
            width, height = candidate[0] - candidate[1], candidate[3] - candidate[2]
            candidates.append(Candidate(candidate[1], candidate[2], width, height))
        end = cv2.getTickCount()
        rospy.logdebug('Cluster:' + str((end - start) / cv2.getTickFrequency()), logger_name="vision_fcnn_handler")
        return candidates

    def _get_raw_candidates(self):
        """
        The old candidate getter, that uses python only clustering. Use cpp version instead.

        :return: a list of candidates [(Candidate), ...]
        """
        out = self.get_fcnn_output()
        r, out_bin = cv2.threshold(out, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        candidates = list()
        # creating points
        # x shape
        xshape = self._image.shape[1]
        xlist = []
        x = 0
        while x < xshape:
            xlist.append(x)
            x += self._pointcloud_stepsize
        # y shape
        yshape = self._image.shape[0]
        ylist = []
        y = 0
        while y < yshape:
            ylist.append(y)
            y += self._pointcloud_stepsize
        # generate carthesian product of list
        points = list(itertools.product(xlist, ylist))
        if self._shuffle_candidate_list:
            random.shuffle(points)
        # expand points
        while points:
            point = points[-1]
            lx, uy = point
            rx, ly = point
            # expand to the left
            if not out_bin[point[1]][point[0]]:
                points.remove(point)
                continue
            next_lx = max(lx - self._expand_stepsize, 0)
            while next_lx > 0 and out_bin[point[1]][next_lx]:
                lx = next_lx
                next_lx = max(lx - self._expand_stepsize, 0)
            # expand to the right
            next_rx = min(rx + self._expand_stepsize, out_bin.shape[1] - 1)
            while next_rx < out_bin.shape[1] - 1 and out_bin[point[1]][next_rx]:
                rx = next_rx
                next_rx = min(rx + self._expand_stepsize, out_bin.shape[1] - 1)
            # expand upwards
            next_uy = max(uy - self._expand_stepsize, 0)
            while next_uy > 0 and out_bin[next_uy][point[0]]:
                uy = next_uy
                next_uy = max(uy - self._expand_stepsize, 0)
            # expand downwards (the lowest y is the highest number for y)
            next_ly = min(ly + self._expand_stepsize, out_bin.shape[0] - 1)
            while next_ly < out_bin.shape[0] - 1 and out_bin[next_ly][point[0]]:
                ly = next_ly
                next_ly = min(ly + self._expand_stepsize, out_bin.shape[0] - 1)
            for i in range(self._candidate_refinement_iteration_count):
                # expand from the middle of the borders of the found candidate
                width, height = rx - lx, ly - uy

                buffer_x = lx + width // 2
                buffer_y = uy + height // 2

                # expand to the left
                next_lx = max(lx - self._expand_stepsize, 0)
                while next_lx > 0 and out_bin[buffer_y][next_lx]:
                    lx = next_lx
                    next_lx = max(lx - self._expand_stepsize, 0)

                # expand to the right
                next_rx = min(rx + self._expand_stepsize, out_bin.shape[1] - 1)
                while next_rx < out_bin.shape[1] - 1 and out_bin[buffer_y][next_rx]:
                    rx = next_rx
                    next_rx = min(rx + self._expand_stepsize, out_bin.shape[1] - 1)

                # expand upwards
                next_uy = max(uy - self._expand_stepsize, 0)
                while next_uy > 0 and out_bin[next_uy][buffer_x]:
                    uy = next_uy
                    next_uy = max(uy - self._expand_stepsize, 0)

                # expand downwards
                next_ly = min(ly + self._expand_stepsize, out_bin.shape[0] - 1)
                while next_ly < out_bin.shape[0] - 1 and out_bin[next_ly][buffer_x]:
                    ly = next_ly
                    next_ly = min(ly + self._expand_stepsize, out_bin.shape[0] - 1)

            # calculate final width and height
            width, height = rx - lx, ly - uy
            candidates.append(Candidate(lx, uy, width, height))
            points.remove(point)
            points = [other_point for other_point in points if not (lx <= other_point[0] <= rx and uy <= other_point[1] <= ly)]
        return candidates

    def get_debug_image(self):
        """
        Returns the fcnn heatmap as ros image message if debug is enabled.

        :return: fcnn heatmap
        """
        if self._debug:
            # Create image message with fcnn heatmap
            return self._cv_bridge.cv2_to_imgmsg(self.get_fcnn_output(), "mono8")


#!/usr/bin/env python
# coding: utf-8

#
#
# # Earth Analytics Education - EA  Python Course Spring 2021

# ## Important  - Assignment Guidelines
#
# 1. Before you submit your assignment to GitHub, make sure to run the entire notebook with a fresh kernel. To do this first, **restart the kernel** (in the menubar, select Kernel$\rightarrow$Restart & Run All)
# 2. Always replace the `raise NotImplementedError()` code with your code that addresses the activity challenge. If you don't replace that code, your notebook will not run.
#
# ```
# # YOUR CODE HERE
# raise NotImplementedError()
# ```
#
# 3. Any open ended questions will have a "YOUR ANSWER HERE" within a markdown cell. Replace that text with your answer also formatted using Markdown.
# 4. **DO NOT RENAME THIS NOTEBOOK File!** If the file name changes, the autograder will not grade your assignment properly.
# 6. When you create a figure, comment out `plt.show()` to ensure the autograder can grade your plots. For figure cells, DO NOT DELETE the code that says `DO NOT REMOVE LINE BELOW`.
#
# ```
# ### DO NOT REMOVE LINE BELOW ###
# student_plot1_ax = nb.convert_axes(plt)
# ```
#
# * Only include the package imports, code, and outputs that are required to run your homework assignment.
# * Be sure that your code can be run on any operating system. This means that:
#    1. the data should be downloaded in the notebook to ensure it's reproducible
#    2. all paths should be created dynamically using the `os.path.join`
#
# ## Follow to PEP 8 Syntax Guidelines & Documentation
#
# * Run the `autopep8` tool on all cells prior to submitting (HINT: hit shift + the tool to run it on all cells at once!
# * Use clear and expressive names for variables.
# * Organize your code to support readability.
# * Check for code line length
# * Use comments and white space sparingly where it is needed
# * Make sure all python imports are at the top of your notebook and follow PEP 8 order conventions
# * Spell check your Notebook before submitting it.
#
# For all of the plots below, be sure to do the following:
#
# * Make sure each plot has a clear TITLE and, where appropriate, label the x and y axes. Be sure to include UNITS in your labels.
#

# ### Add Your Name Below
# **Your Name: Eric Gottlieb (@esgeo)**

#

# ---

# # Week 04 and 05 Homework - Automate NDVI Workflow
#
# For this assignment, you will write code to generate a plot of the mean normalized difference vegetation index (NDVI) for two different sites in the United States across one year of data:
#
# * San Joaquin Experimental Range (SJER) in Southern California, United States
# * Harvard Forest (HARV) in the Northeastern United States
#
# The data that you will use for this week is available from **earthpy** using the following download:
#
# `et.data.get_data('ndvi-automation')`
#
# ## Assignment Goals
#
# Your goal in this assignment is to create the most efficient and concise workflow that you can that allows for:
#
# 1. The code to scale if you added new sites or more time periods to the analysis.
# 2. Someone else to understand your workflow.
# 3. The LEAST and most efficient (i.e. runs fast, minimize repetition) amount of code that completes the task.
#
# ### HINTS
#
# * Remove values outside of the landsat valid range of values as specified in the metadata, as needed.
# * Keep any output files SEPARATE FROM input files. Outputs should be created in an outputs directory that is created in the code (if needed) and/or tested for.
# * Use the functions that we demonstrated during class to make your workflow more efficient.
# * BONUS - if you  chose - you can export your data as a csv file. You will get bonus points for doing this.
#
#
# ## Assignment Requirements
#
# Your submission to the GitHub repository should include:
# * This Jupyter Notebook file (.ipynb) with:
#     * The code to create a plot of mean NDVI across a year for  2 NEON Field Sites:
#         * NDVI on the x axis and formatted dates on the y for both NEON sites on one figure/axis object
#     * The **data should be cleaned to remove the influence of clouds**. See the [earthdatascience website for an example of what your plot might look like with and without removal of clouds](https://www.earthdatascience.org/courses/earth-analytics-python/create-efficient-data-workflows/).
# * BONUS: Create one output `.csv` file that has 3 columns - NDVI, Date and Site Name - with values for SJER and HARV.
#
# Your notebook should:
# * Have *at least* 2 well documented and well named functions with docstrings.
# * Include a Markdown cell at the top of the notebook that outlines the overall workflow using pseudocode (i.e. plain language, not code)
# * Include additional Markdown cells throughout the notebook to describe:
#     * the data that you used - and where it is from
#     * how data are being processing
#     * how the code is optimized to run fast and be more concise

# # Replace this cell with your pseudocode  for this workflow
#
# If you happen to be a diagram person a diagram is ok too
#
#

# # Pseudocode description
# - Step 1: Import libraries
# - Step 2: Import data
# - Step 3: Create working directories
# - Step 4: Establish functions to perform batch operations on raster datasets
#     - Step 4a: Function to clean data in individual bands based on expected values (in this exercise 0-10000)
#     - Step 4b: Function to crop rasters for a scene that is used for a normalized difference calculation and pixel qa-based mask. The specific code is used to calculate NDVI and mask based on cloud effects using raster bands specified in a list object
# - Step 5: From the entirety of the dataset, create a sorted glob list object that has all scenes to be used
# - Step 6: Parse data from directories/filenames that will go in dataframe
# - Step 7: Create cropping object that will be used in function 4b
# - Step 8: Create list object with bands for NDVI calc
# - Step 9: Create path object for cloud mask qa used in function 4b
# - Step 10: Make for loop to first function to clean band 4 and 5 rasters
# - Step 11: Create list object of pixel qa raster values to mask cloud effects (used in function 4b)
# - Step 12: Create cloud effect-cleaned and cropped ndvi object using mask_crop_ndvi function
# - Step 13: Calculate np.nanmean value from Step 12 output
# - Step 14: Make datetime indexed pandas dataframe from Step 6 and Step 13 outputs
# - Step 15: Retool all of Steps 7-14 into a loop function that can applied to Step 6 scene list
# - Step 16: Concat loop output data into dataframe and deal with NaN values from cloud cover
# - Step 17: Plot concatonated dataframe using for loop that allows data to be groupby() 'site' and excludes NaN values
# - Step 18: Export .csv file

# In[1]:

# Autograding imports - do not modify this cell
import matplotcheck.autograde as ag
import matplotcheck.notebook as nb
import matplotcheck.timeseries as ts
from datetime import datetime

# In[2]:

# Import needed packages in PEP 8 order
# and no unused imports listed (10 points total)
import os
from glob import glob

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import geopandas as gpd
import rasterio as rio
#from rasterio.plot import plotting_extent
import rioxarray as rxr
import xarray as xr
import earthpy as et
import earthpy.plot as ep
import earthpy.spatial as es
import earthpy.mask as em
import warnings

# Designate working directory path as object.
mypath = os.path.join(et.io.HOME, 'earth-analytics', 'data')

# Change working directory to specified path.
if os.path.exists(mypath):
    os.chdir(mypath)
else:
    os.makedirs(mypath),
    os.chdir(mypath),
    print(
        "The path " + os.getcwd() + " did not exist, but has now been created.")

# Download data
et.data.get_data('ndvi-automation')

# In[3]:

# DO NOT MODIFY THIS CELL
# Tests that the working directory is set to earth-analytics/data

path = os.path.normpath(os.getcwd())
student_wd_parts = path.split(os.sep)

if student_wd_parts[-2:] == ['earth-analytics', 'data']:
    print("\u2705 Great - it looks like your working directory is set correctly to ~/earth-analytics/data")
else:
    print("\u274C Oops, the autograder will not run unless your working directory is set to earth-analytics/data")

# # Figure 1: Plot 1 - Mean NDVI For Each Site Across the Year (50 points)
#
# Create a plot of the mean normalized difference vegetation index (NDVI) for the two different sites in the United States across the year:
#
# * NDVI on the x axis and formatted dates on the y for both NEON sites on one figure/axis object.
# * Each site should be identified with a different color in the plot and legend.
# * The final plot **data should be cleaned to remove the influence of clouds**.
# * Be sure to include appropriate title and axes labels.
#
# Add additional cells as needed for processing data (e.g. defining functions, etc), but be sure to:
# * follow the instructions in the code cells that have been provided to ensure that you are able to use the sanity check tests that are provided.
# * include only the plot code in the cell identified for the final plot code below

# ## Task 1:
#
# In the cell below, create a single dataframe containing MEAN NDVI, the site name,
# and the date of the data for the HARV site
# scene `HARV/landsat-crop/LC080130302017031701T1-SC20181023151837`.  The column names for the  final
# DataFrame should be`mean_ndvi`, and `site`, and the data should be **indexed on the date**.
#
# Use the functions that we reviewed in class (or create your own versions of them) to implement your code
#
# ### In the Cell below Place  All Functions Needed to Run this Notebook (20 points)

# In[4]:

### DO NOT REMOVE THIS LINE OR EDIT / MOVE THIS CELL ###
start_time = datetime.now()

# In[5]:

# Functions

# Function to open specified bands and mask any values outside specified range
def open_clean_bands(band_path,
                     valid_range=None,):
    """Open/mask single landsat band using a valid reflectance value range.

    Parameters
    -----------
    band_path : string
        A path to the array to be opened
    valid_range : tuple (optional)
        A tuple of min and max values of the data. Default = None

    Returns
    -----------
    band : xarray DataArray
        An xarray DataArray w values to be masked = 1 for True (Boolean)
    """

    band = rxr.open_rasterio(band_path, masked=True).squeeze()

    # Only run this step if a valid range tuple is provided
    if valid_range:
        mask = ((band < valid_range[0]) | (band > valid_range[1]))
        band = band.where(~xr.where(mask, True, False))

    return band

# Function that crop rasters for a scene that is
# used for a normalized difference calculation and pixel qa-based mask
# This specific code is used to calculate NDVI and mask based on cloud effects
def mask_crop_ndvi(all_bands,
                   crop_bound,
                   pixel_qa_path,
                   vals):
    """Using specificed crop geometry, calculates NDVI and applied qa-mask.

    Parameters
    -----------
    all_bands : list
        A list of xarray objects for landsat bands used in ndvi calculation
    crop_bound: geopandas GeoDataFrame
        A gpd dataframe to crop the raster data using rasterio mask().
    pixel_qa_path: string
        A path to a pixel qa tif file.
    vals: list
        A list of values needed to create the cloud mask

    Returns
    -----------
    ndvi_crop_mask : Xarray Dataset
        a cropped and masked xarray object containing NDVI values
    """

    # Make object that is crop boundary geometry
    crop_json = crop_bound.geometry

    # Open and clip qa layer using above object
    pixel_qa = rxr.open_rasterio(
                pixel_qa_path[0], masked=True).rio.clip(
                crop_json, from_disk=True).squeeze()

    # Calculate normalized difference (in this case NDVI)
    # all_bands[1] is landsat band 5 (NIR) and all_bands[0] is band 4 (red)
    ndvi_xr = (all_bands[1]-all_bands[0]) / (all_bands[1]+all_bands[0])

    # Create cropped norm. diff. xarray object using same crop boundary object
    ndvi_cropped = ndvi_xr.rio.clip(crop_json, from_disk=True)

    # Apply pixel qa-based mask to cropped xarray
    ndvi_crop_mask = ndvi_cropped.where(~pixel_qa.isin(vals))

    # Output xarray
    return ndvi_crop_mask

# The code cell below is an exercise in directory parsing, data extraction, and data processing on a single scene. For this exercise the scene is found in the directory:
# earth-analytics/data/ndvi-automation/HARV/landsat-crop/LC080130302017031701T1-SC20181023151837
#
# At the beginning of the code cell, a sorted glob list of all the sites in the HARV and SJER directories is created. For the purpose of this exercise, the 5th scene in the sorted list is the one we are asked to work with, and the next block of code deals with parsing the site name (HARV) and the date (2017-03-17) from that scene.
#
# The extent of the vector data in the dataset is then used to create a cropping boundary for the raster data.
#
# Next the open_clean_bands and mask_crop_ndvi functions are run on the specified scene.
#
# Finally a pandas dataframe is created from the date, mean NDVI value and site name, which is then indexed by date.

# In[6]:

# Path/directory configurations- re-used for Task 2 (loop)
path = os.path.join("ndvi-automation", "sites")

all_sites = sorted(glob(os.path.join(path, "*", "*", "*/")))

# Parse site name for Task 1 (single scene)
# Normpath() of specified scene from sorted glob list
scene_5_dir = os.path.normpath(all_sites[4])

# Split path directories into individual string variables in a list
path_components = scene_5_dir.split(os.sep)

# Make object from 3rd (index[2]) item in list
site = path_components[2]

# Parse date for Task 1 (single scene)
# Parse specified scene filename as string
scene_5 = os.path.basename(os.path.normpath(all_sites[4]))

# Parse date from filename
date = scene_5[10:18]

# Extract crop geometry for Task 1 (single scene)
# Designate directory containing vector file used to crop
vector_dir = os.path.join(path, site, "vector")

# Create object that is path to shapefile by joining pre-existing objects
crop_extent_path = os.path.join(vector_dir,  site + "-crop.shp")

#Create crop boundary using geopandas to read shapefile in above path
crop_bound = gpd.read_file(crop_extent_path)

# For loop to run open_clean_bands function to clean band 4 and 5 rasters
# Create empty object to populate with .append
all_bands = []

# Create list object with bands for NDVI calc for Task 1 (single scene)
band_paths = sorted(
    glob(os.path.join(scene_5_dir, "*band*[4-5].tif")))

# For loop that runs open_clean_bands function used in NDVI calculation
# and appends the cleaned band rasters to the newly created all_bands object
for aband in band_paths:
    all_bands.append(open_clean_bands(band_path=aband,
                                      valid_range=(0, 10000)))

# Create cleaned ndvi object using mask_crop_ndvi function

# Create list objects of pixel qa raster values to mask cloud effects
high_cloud_confidence = em.pixel_flags["pixel_qa"]["L8"]["High Cloud Confidence"]
cloud = em.pixel_flags["pixel_qa"]["L8"]["Cloud"]
cloud_shadow = em.pixel_flags["pixel_qa"]["L8"]["Cloud Shadow"]

# Combine list objects into 1d array to be used in mask_crop_ndvi function
vals = cloud_shadow + cloud + high_cloud_confidence

# Create path object for cloud mask qa raster for Task 1 (single scene)
pixel_qa_path = glob(os.path.join(scene_5_dir, "*qa*"))

# The resultant object has been cropped and cleaned for cloud effects
ndvi_clean = mask_crop_ndvi(all_bands=all_bands,
                            crop_bound=crop_bound,
                            pixel_qa_path=pixel_qa_path,
                            vals=vals)

# Create dataframe of specified columns, populating mean_ndvi using np.nanmean
scene_5_df = pd.DataFrame(columns=["date", "mean_ndvi", "site"], data=[
                          [date, np.nanmean(ndvi_clean), site]])

# Convert date to datetime format
scene_5_df['date'] = pd.to_datetime(scene_5_df['date'], format='%Y%m%d')

# Set date as index
scene_5_df.set_index('date', inplace=True)

# Call dataframe
scene_5_df

# In[7]:

# This cell  is testing your data output above

student_ndvi_ts_single_site = _

single_scene_points = 0

# Ensure the data is stored in a dataframe.
if isinstance(student_ndvi_ts_single_site, pd.DataFrame):
    print('\u2705 Your data is stored in a DataFrame!')
    single_scene_points += 1
else:
    print('\u274C It appears your data is not stored in a DataFrame. ',
          'To see what type of object your data is stored in, check its type with type(object)')

# Ensure that the date column is the index
if isinstance(student_ndvi_ts_single_site.index, pd.core.indexes.datetimes.DatetimeIndex):
    print('\u2705 You have the index set to the date column!')
    single_scene_points += 2
else:
    print('\u274C You do not have the index set to the date column.')

# Ensure that the date column is datetime
if isinstance(student_ndvi_ts_single_site.index[0], pd._libs.tslibs.timestamps.Timestamp):
    print('\u2705 The data in your date column is datetime!')
    single_scene_points += 2
else:
    print('\u274C The data in your date column is not datetime.')

# Ensure the site name is correct
if student_ndvi_ts_single_site.site.values[0] == 'HARV':
    print('\u2705 You have the correct site name!')
    single_scene_points += 5
else:
    print('\u274C You do not have the correct site name.')

if np.allclose(0.281131628228094, student_ndvi_ts_single_site.mean_ndvi.values[0]):
    print('\u2705 You have the correct mean NDVI value!')
    single_scene_points += 5
else:
    print('\u274C You do not have the correct mean ndvi value.')

print("\n \u27A1 You received {} out of 15 points for creating a dataframe.".format(
    single_scene_points))
single_scene_points

# ## Task 2:
#
# In the cell below, process all of the landsat scenes. Create a DataFrame that contains the following
# information for each scene
#
#
# |   | index  | site  | mean_ndvi  |
# |---|---|---|---|
# | Date  |   |   |   |
# |  2017-01-07  | 0  | SJER  | .4  |
#
# Be sure to call your dataframe at the end of the cell to ensure autograding works.
# HINT: FOR THIS STEP, leave any rows containing missing values (`NAN`).

# The code cell below is basically just an expansion of the single scene code above into a for loop that runs the NDVI calculation on every scene in the directory.
#
# Intuitively it makes sense that what I did for the single scene and have repeated here in a loop could be functionalized. However, I am still a little shaky on how to execute this, and unfortunately resorted to being repetitive.
#
# The only real difference between the code cell below and the one for the single scene is that I added a pd.reset_index line so that there was an index column in the pandas df.

# In[8]:

# Loop through each site directory to create a date-indexed df
# with site and mean_ndvi variables

# Create blank object to populate
alldata = []

# For loop that extracts objects to be converted to df variables
for site_dir in all_sites:
    # Split path of each scene directory
    path_components = site_dir.split(os.sep)

    # Assign 'site' string from above list as object
    site = path_components[2]

    # Parse date variable from each scene filename in list
    date = os.path.basename(
        os.path.normpath(site_dir))[10:18]

    # Make pixel qa raster directory object for each scene in list
    pixel_qa_path = glob(
        os.path.join(site_dir, "*qa*"))

    # Make vector directory path object
    vector_dir = os.path.join(
        path, site, "vector")

    # Make crop_extent directory object from shapefile of respective scene
    crop_extent_path = os.path.join(
        vector_dir, site + "-crop.shp")

    # Use geopandas to create crop_bound directory object
    crop_bound = gpd.read_file(crop_extent_path)

    # Make dir_name that is the respective scene name
    dir_name = os.path.basename(
        os.path.normpath(site_dir))

    # Create sorted glob list of bands needed for norm diff calculation
    band_paths = sorted(
        glob(os.path.join(site_dir, "*band*[4-5].tif")))

    # Re-create blank object to populate in nested loop
    all_bands = []

    # Nested for loop to run open_clean_bands function on each band from given scene
    for aband in band_paths:
        # runs open_clean_bands function used in NDVI calculation for each scene
        # and appends the cleaned band rasters to the re-created all_bands object
        all_bands.append(open_clean_bands(
            band_path=aband, valid_range=(0, 10000)))

    # Create cleaned ndvi object using mask_crop_ndvi function
    # The resultant object has been cropped and cleaned for cloud effects
    ndvi_clean = mask_crop_ndvi(all_bands=all_bands,
                                crop_bound=crop_bound,
                                pixel_qa_path=pixel_qa_path,
                                vals=vals)

    # Make an row of data for each scene to be appended
    # Include warning suppression for np.nanmean(df) mean of empty slice
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", category=RuntimeWarning)
        all_scenes = pd.DataFrame(columns=["date", "site",
                                  "mean_ndvi"],
                                  data=[[date, site, np.nanmean(ndvi_clean)]]
                                  )

    # Added this line so the dataframe was identical to the example at top
    all_scenes.reset_index(inplace = True)

    # Format parsed date string into datetime object
    all_scenes['date'] = pd.to_datetime(
        all_scenes['date'], format='%Y%m%d')

    # Populate alldata object created just before start of loop
    alldata.append(all_scenes)

# Concat data into pandas dataframe
all_data = pd.concat(alldata)

# Set index by date
all_data_indexed = all_data.set_index("date")

# Call dataframe object
all_data_indexed

# In[9]:

# Last sanity check before creating your plot (10 points)

# Ensure that you call your dataframe at the bottom of the cell above
# and that it has columns called: mean_ndvi and site

# Ensure the data is stored in a dataframe.
student_ndvi_df = _

df_points = 0

if isinstance(student_ndvi_df, pd.DataFrame):
    print('\u2705 Your data is stored in a DataFrame!')
    df_points +=2
else:
    print('\u274C It appears your data is not stored in a DataFrame. ',
          'To see what type of object your data is stored in, check its type with type(object)')

# Check that dataframe contains the appropriate number of NAN values
if student_ndvi_df.mean_ndvi.isna().sum() == 15:
    print('\u2705 Correct number of masked data values!')
    df_points +=2
else:
    print('\u274C The amount of null data in your dataframe is incorrect.')

# Ensure that the date column is the index
if isinstance(student_ndvi_df.index, pd.core.indexes.datetimes.DatetimeIndex):
    print('\u2705 You have the index set to the date column!')
    df_points +=3
else:
    print('\u274C You do not have the index set to the date column.')

# Ensure that the date column is datetime
if isinstance(student_ndvi_df.index[0], pd._libs.tslibs.timestamps.Timestamp):
    print('\u2705 The data in your date column is datetime!')
    df_points +=3
else:
    print('\u274C The data in your date column is not datetime.')

# Output for timer, # DO NOT MODIFY
end_time = datetime.now()
total_time = end_time - start_time
print(
    "Your total run time for processing the data was {0}.".format(total_time))

print("\n \u27A1 You received {} out of 10 points for creating a dataframe.".format(
    df_points))

df_points

# The cell below plots the data (Mean NDVI result vs date). The plot executes by looping though the pandas dataframe and assigning a symbol color based on the site (green for HARV, brown for SJER).

# In[10]:

# Eliminate NaN values for plotting
all_data_indexed.dropna(inplace=True)

# Reset index for plotting
all_data_indexed.reset_index(inplace=True)

# Call fig object
fig, ax = plt.subplots(figsize=(12, 10))

# For loop to groupby 'site' and color datapoints accordingly
for title, group in all_data_indexed.groupby('site'):
    if title == "HARV":
        set_color = "green"
    else:
        set_color = "brown"

    # Plot grouped data
    group.groupby('site').plot(ax=ax,
                               x='date',
                               y='mean_ndvi',
                               title="Mean Landsat NDVI values\nHarvard Forest (HARV) and San Joaquin Experimental Ranges (SJER)",
                               xlabel="Date (yyyy-mm)",
                               ylabel="Mean NDVI value",
                               label=title,
                               style='*--',
                               color=set_color)

### DO NOT REMOVE LINES BELOW ###
final_masked_solution = nb.convert_axes(plt, which_axes="current")

# In[11]:

# Ignore this cell for the autograding tests

# In[12]:

# Ignore this cell for the autograding tests

# # Question 1 (10 points)
#
# Imagine that you are planning NEON’s upcoming flight season to capture remote sensing data in these locations and want to ensure that you fly the area when the vegetation is the most green.
#
# When would you recommend the flights take place for each site?
#
# Answer the question in 2-3 sentences in the Markdown cell below.

# Based on the data above, the flights should occur during different times of the year to take advantage of maximum greenness and best weather windows. The data show for SJER in 2017, max mean NDVI was observed between late February and early April. Timing the absolute maximum greenup would likely be highly dependent on the timing, quantity and quality (i.e., rain, snow, duration) of winter precipitation that had just been received. For example, a larger snowpack may delay greenup, but a dry winter season might cause early greenup.
#
# For the Harvard Forest site, max greenup in 2017 was ongoing from late spring to early fall. However, the maximum mean value was observed in early summer when the data are more sparse (due to cloud effects). Conversely, early fall had almost as high mean NDVI values and the data were less affected by cloudcover. Potentially longer lead climate prediction products from NOAA could help assess the risk of cloud interference in early summer vs early fall for an upcoming season (at least you would have someone else to blame!)

# # Question 2 (10 points)
#
# How could you modify your workflow to look at vegetation changes over time in each site?
#
# Answer the question in 2-3 sentences in the Markdown cell below.

# This workflow could be easily modified to look at normalized differences in other multispectral parameters such as changes in CIR or (in case of a fire) NBR values. The workflow is designed such that the `mask_crop_ndvi` function can be retooled to use different band inputs. This would require one or two basic steps:
#
# The first is that the band_paths object would need to be modified to include the appropriate bands, which could be easily done by just changing index values to use the required bands
# band_paths = sorted(glob(os.path.join(site_dir, "*band*[4-5].tif")))
#
# In this case, because the NBR calcuation is also a normalized difference, one could actually use the same `mask_crop_ndvi` function but simply just change some object names for bookkeeping
#
# The second involves changing the function because there are more inputs, as would be the case in looking at something like mean CIR over time. Because there are 3 bands (instead of 2) used for CIR analyses, the function would need a CIR calcuation line of code added that uses the appropriate bands.

# # Do not edit this cell! (10 points)
#
# The notebook includes:
# * additional Markdown cells throughout the notebook to describe:
#     * the data that you used - and where it is from
#     * how data are being processing
#     * how the code is optimized to run fast and be more concise

# # Do not edit this cell! (20 points)
#
# The notebook will also be checked for overall clean code requirements as specified at the **top** of this notebook. Some of these requirements include (review the top cells for more specifics):
#
# * Notebook begins at cell [1] and runs on any machine in its entirety.
# * PEP 8 format is applied throughout (including lengths of comment and code lines).
# * No additional code or imports in the notebook that is not needed for the workflow.
# * Notebook is fully reproducible. This means:
#    * reproducible paths using the os module.
#    * data downloaded using code in the notebook.
#    * all imports at top of notebook.

# ## BONUS - Export a  .CSV File to Share (10 points possible)
#
# This is optional - if you export a **.csv** file with the columns specified above: Site, Date and NDVI Value you can get an additional 10 points.
#
# * FULL CREDIT: File exists in csv format and contains the columns specified.
# We will check your github repo for this file!
#

# In[13]:

# Output csv of indexed pandas dataframe

# Create output path for csv file
output_path = os.path.join("ndvi-automation",
                           "outputs")

if not os.path.isdir(output_path):
    os.mkdir(output_path)

# Change working directory to output path
os.chdir(output_path)

# Export to .csv, include nan values as NaN instead of default
all_data_indexed.to_csv('mean-ndvi.csv', na_rep='NaN')