# -------------------------------------------------------------------------------------------------
# This file contains various functions which are used to filter the captions generated by DenseCap. 
# -------------------------------------------------------------------------------------------------

# Importing numPy for manipulating array data
import numpy as np
# Importing json for handling JSON data
import json

""" Function for filtering out captions if they contain a specific word or sentence. The function does this using list
comprehension to compare two sets of lists, the captions list and a list containing the words to filter out. If any of
the words from the filter list are in the captions list then the index positions of those elements are returned. Using
the index positions the captions are then deleted from the captions list. The confidence score values and bounding
boxes associated with those captions are also deleted using the index positions. """
# Takes the captions list, confidence scores list, bounding boxes list and the filter list as it's arguments
def filterList(captions, scores, boxes, wordsToFilter):
	# Using list comprehension to check if any of the words from the filter list appear in any of the elements from
	# the captions list using the function "any" and the operator "in". However instead of returning the values of the
	# matching elements in the captions list the index positions of those matching elements are returned instead as the
	# captions list is passed into an enumerate function.
	indexes = [i for i, s in enumerate(captions) if any(w in s for w in wordsToFilter)]
	# Deleting the matching elements from the captions list by passing the captions list and the index positions
	# into the method np.delete, this then returns a new NumPy array  
	filtered_captions = np.delete(captions, indexes)
	# Deleting the confidence score elements associated with the captions
	filtered_scores = np.delete(scores, indexes)
	# Deleting the bounding boxes associated with the captions with the axis set to 0 as this is a 2D array
	filtered_boxes = np.delete(boxes, indexes, axis=0)
	# Returning the new captions, scores and bounding boxes arrays 
	return filtered_captions, filtered_scores, filtered_boxes

""" Function for removing duplicate captions, this function also uses list comprehension, however this time
comprehension is being used to find duplicate elements within a list. Like the previous function the comprehension
also returns the index positions of the matching elements which are then used to delete the duplicates. This function
is designed to be used after the function above as it takes NumPy arrays as it's arguments instead of python lists. """
# Takes the captions array, scores array and bounding boxes array as it's arguments
def removeDuplicates(captions, scores, boxes):
	# Using comprehension to get the index positions of duplicate elements in the captions array. The duplicates
	# are found by checking for elements that have already appeared in the captions array using the operator
	# "in" and stepping through each element in the array using slicing starting from the beginning of the array.
	duplicates = [idx for idx, val in enumerate(captions) if val in captions[:idx]]
	# Deleteing the duplicate captions
	duplicate_captions = np.delete(captions, duplicates)
	# Deleting the scores associated with the duplicate captions
	duplicate_scores = np.delete(scores, duplicates)
	# Deleting the bounding boxes associated with the duplicate captions
	duplicate_boxes = np.delete(boxes, duplicates, axis=0)
	# Returning the new caption, scores and bounding boxes arrays 
	return duplicate_captions, duplicate_scores, duplicate_boxes

""" Function for filtering out captions below a score threshold """
# Takes the captions array, scores array, bounding boxes array and a threshold score as it's arguments
def scoreThreshold(captions, scores, boxes, threshold):
	# Using the method np.delete and np.argwhere to remove the elements from the score array which are below
	# the threshold
	highest_scores = np.delete(scores, np.argwhere(scores <= threshold))
	# Passing the length of the new score array into the captions array. This then removes captions below the threshold
	# score as the elements in the captions array are in the same order as the scores array which is based on the
	# confidence score value for a given caption going from highest to lowest score. The caption array is then converted
	# back to a python list so that it can be converted to JSON format.
	captions_highest_scores = captions[:len(highest_scores)].tolist()
	# Passing the length of the new score array into the bounding boxes array to remove the bounding boxes associated
	# with the captions below the threshold and converting back to a python list. This array is also in the same order
	# as the score array.
	boxes_highest_scores = boxes[:len(highest_scores)].tolist()
	# Returning the new captions and bounding boxes lists 
	return captions_highest_scores, boxes_highest_scores

""" Function for adding the word "and" to the last element in the captions list """
# Takes the captions list as it's argument
def changeLastElement(list):
	# Retriving the value from the last element in the list and adding the word "and" to it
	change_last_element = 'and ' + list[-1]
	# Removing the last element from the captions list using the pop function
	list.pop()
	# Adding the new element to the end of the captions list using the append function
	list.append(change_last_element)
	# Returning the edited list
	return list

""" Function for converting the captions and bounding boxes lists into a JSON object. The JSON object also contains
the information which states if the image is a Bing Maps street view image and if the image is of a street. """
# Takes the captions list, bounding boxes list and two boolean variables as its arguments
def sendAsJson(captions, boxes, isStreetView, isStreet):
	# Converting the data into a python dictionary
	as_dictionary = {'results': { 'captions': captions,
	'boxes': boxes, 'isStreetView': isStreetView, 'isStreet': isStreet}}
	# Converting the python dictionary into JSON format
	to_json = json.dumps(as_dictionary)
	# Returning the JSON data
	return (to_json)

""" Function for filtering captions of images which are not Bing Maps street view images. The function works by
combining the five functions above to filter the captions generated by DenseCap. """
# Takes the captions list, confidence scores list, bounding boxes list and the filter list as it's arguments
def filteredCaptions(captions, scores, boxes):
	
	# Defining the list of words to filter out from the captions list
	wordsToFilter = [ '<UNK>', 'no']
  
	# Passing the captions list, confidence scores list, bounding boxes list, and the words to filter out list into the
	# filterList function then storing the values returned by the function 
	filtered_caps, filtered_scores, filtered_boxes = filterList(captions, scores, boxes, wordsToFilter)
	
	# Passing the values returned by the function above into the removeDuplicates function and then storing the values
	# returned by the function
	dup_caps, dup_scores, dup_boxes = removeDuplicates(filtered_caps, filtered_scores, filtered_boxes)

  # Passing the values returned by the function above and a threshold score of 0.5 into the scoreThreshold function then storing the values returned by the function 
	caps_thresh_score, boxes_thresh_score = scoreThreshold(dup_caps, dup_scores, dup_boxes,0.5)

  # Passing the captions list returned by the function above into the changeLastElement function then storing the
	# value returned by the function
	caption = changeLastElement(caps_thresh_score)
   
	# Passing the captions list returned by the function above and the bounding boxes list returned by the scoreThreshold function into the sendAsJson function with both boolean values set to false as this function is not
	# for Bing Maps street view images
	json_file = sendAsJson(caption, boxes_thresh_score, False, False)
	
	# Returning the JSON data
	return (json_file)

""" Function for filtering captions of images which are Bing Maps street view images. The functionality is similar to
the function above however it also checks whether DenseCap thinks the image is of city street. """
def filteredstreetViewCaptions(captions, scores, boxes):

	# Defining the list of words to filter out, this list also contains complete sentences as well as words
	wordsToFilter = ['car', 'cars', 'motorcycle', 'motorcycles', 'bike', 'bicycle', 'bicycles', 'bus', 'van', 'vans', 'truck', 'trucks', 'train', 'trains', 'boat', 'plan', 'people', 'person', 'man', 'women', 'sky', 'cloud', 'clouds', 'sun', 'day', 'daytime', 'shadow', 'wet', 'snow', 'photo', 'runway', 'no', 'blue', 'water', 'laptop', 'phone', 'a road in the road', 'a road with a lot of it', 'a road on the road', 'a road is on the road', 'a scene in the background', '<UKN>']
  
	# Filtering out captions that contain any of the words or sentences from the list above
	filtered_caps, filtered_scores, filtered_boxes = filterList(captions, scores, boxes, wordsToFilter)
	# Removing duplicate captions
	dup_caps, dup_scores, dup_boxes = removeDuplicates(filtered_caps, filtered_scores, filtered_boxes)
	# Removing captions that have a confidence score below 0
	caps_thresh_score, boxes_thresh_score = scoreThreshold(dup_caps, dup_scores, dup_boxes,0)

	# This list is used to check whether DenseCap thinks the image is of a city street by checking if any of these sentences are in the captions list
	filter_street_caps = ['a city scene', 'a street scene', 'a city street', 'a street', 'a city street scene','a scene of a city', 'this is a picture of a city', 'a large city scene', 'a large city street']

	# Checking if any of the sentences in the list above are in the captions list
	if any(i for i in caps_thresh_score if any(s in i for s in filter_street_caps)):
		# Using the same comprehension technique from the filterList function to return the index positions of the
		# matching elements 
		indexes = [i for i, s in enumerate(caps_thresh_score) if any(w in s for w in filter_street_caps)]
		# Deleting the matching elements and converting back to a list
		captions = np.delete(caps_thresh_score, indexes).tolist()
		# Deleting the corresponding bounding boxes and converting back to a list
		boxes = np.delete(boxes_thresh_score, indexes, axis=0).tolist()
		# Changing the last element
		caption = changeLastElement(captions)
		# Converting the data to JSON and setting both boolean variables to true as this is a Bing Maps street view image
		# and DenseCap thinks the image is of a city street 
		json_file = sendAsJson(caption,boxes, True, True)
		# Returning the JSON data
		return (json_file)
	# if none of the sentences from the filter_street_caps list appear in the captions list
	else:
		# Changing the last element
		caption = changeLastElement(caps_thresh_score)
		# Converting the data to JSON with the first boolean variable set to true as this is a Bing Maps street view image
		# however DenseCap does not think the image is of a city street so the second boolean variable is set to false
		json_file = sendAsJson(caption, boxes_thresh_score, True, False)
		# Returning the JSON data
		return (json_file)