# coding=utf-8
# =============================================================================
#  Copyright © 2017 FLIR Integrated Imaging Solutions, Inc. All Rights Reserved.
#
#  This software is the confidential and proprietary information of FLIR
#  Integrated Imaging Solutions, Inc. ("Confidential Information"). You
#  shall not disclose such Confidential Information and shall use it only in
#  accordance with the terms of the license agreement you entered into
#  with FLIR Integrated Imaging Solutions, Inc. (FLIR).
#
#  FLIR MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
#  SOFTWARE, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
#  PURPOSE, OR NON-INFRINGEMENT. FLIR SHALL NOT BE LIABLE FOR ANY DAMAGES
#  SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
#  THIS SOFTWARE OR ITS DERIVATIVES.
# =============================================================================
#
# Acquisition.py shows how to acquire images. It relies on
# information provided in the Enumeration example. Also, check out the
# ExceptionHandling and NodeMapInfo examples if you haven't already.
# ExceptionHandling shows the handling of standard and Spinnaker exceptions
# while NodeMapInfo explores retrieving information from various node types.
#
# This example touches on the preparation and cleanup of a camera just before
# and just after the acquisition of images. Image retrieval and conversion,
# grabbing image data, and saving images are all covered as well.
#
# Once comfortable with Acquisition, we suggest checking out
# AcquisitionMultipleCamera, NodeMapCallback, or SaveToAvi.
# AcquisitionMultipleCamera demonstrates simultaneously acquiring images from
# a number of cameras, NodeMapCallback serves as a good introduction to
# programming with callbacks and events, and SaveToAvi exhibits video creation.

"""
This is a script to acquire video from a Point Gray Video camera.

ToDo: Wrap this in a class, make sure it runs in its own thread, lots more ...
"""

import os, time
import PySpin

# abb
#import matplotlib
#matplotlib.use("Qt4agg") # or "Qt5agg" depending on you version of Qt
'''
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
'''

import cv2

NUM_IMAGES = 100  # number of images to grab


myRunning = True
def myCloseCallback(evt):
	print('Closed Figure!')
	global myRunning
	myRunning = False
	'''
	cam.EndAcquisition()
	cam.DeInit()
	'''

image_data = None
imgplot = None
line = None # what I get back from imShow
def myInit():
	print('myInit:', i)
	imgplot.set_data([], [])
	return imgplot,

def myUpdate(i):
	print('myUpdate:', i)
	global image_data
	imgplot.set_data(image_data)
	return imgplot,

def acquire_images(cam, nodemap, nodemap_tldevice):
	"""
	This function acquires and saves 10 images from a device.

	:param cam: Camera to acquire images from.
	:param nodemap: Device nodemap.
	:param nodemap_tldevice: Transport layer device nodemap.
	:type cam: CameraPtr
	:type nodemap: INodeMap
	:type nodemap_tldevice: INodeMap
	:return: True if successful, False otherwise.
	:rtype: bool
	"""

	print('*** IMAGE ACQUISITION ***\n')
	try:
		result = True

		# Set acquisition mode to continuous
		#
		#  *** NOTES ***
		#  Because the example acquires and saves 10 images, setting acquisition
		#  mode to continuous lets the example finish. If set to single frame
		#  or multiframe (at a lower number of images), the example would just
		#  hang. This would happen because the example has been written to
		#  acquire 10 images while the camera would have been programmed to
		#  retrieve less than that.
		#
		#  Setting the value of an enumeration node is slightly more complicated
		#  than other node types. Two nodes must be retrieved: first, the
		#  enumeration node is retrieved from the nodemap; and second, the entry
		#  node is retrieved from the enumeration node. The integer value of the
		#  entry node is then set as the new value of the enumeration node.
		#
		#  Notice that both the enumeration and the entry nodes are checked for
		#  availability and readability/writability. Enumeration nodes are
		#  generally readable and writable whereas their entry nodes are only
		#  ever readable.
		#
		#  Retrieve enumeration node from nodemap

		# In order to access the node entries, they have to be casted to a pointer type (CEnumerationPtr here)
		node_acquisition_mode = PySpin.CEnumerationPtr(nodemap.GetNode('AcquisitionMode'))
		if not PySpin.IsAvailable(node_acquisition_mode) or not PySpin.IsWritable(node_acquisition_mode):
			print('Unable to set acquisition mode to continuous (enum retrieval). Aborting...')
			return False

		# Retrieve entry node from enumeration node
		node_acquisition_mode_continuous = node_acquisition_mode.GetEntryByName('Continuous')
		#node_acquisition_mode_continuous = node_acquisition_mode.GetEntryByName('SingleFrame')
		if not PySpin.IsAvailable(node_acquisition_mode_continuous) or not PySpin.IsReadable(node_acquisition_mode_continuous):
			print('Unable to set acquisition mode to continuous (entry retrieval). Aborting...')
			return False

		# Retrieve integer value from entry node
		acquisition_mode_continuous = node_acquisition_mode_continuous.GetValue()

		# Set integer value from entry node as new value of enumeration node
		node_acquisition_mode.SetIntValue(acquisition_mode_continuous)

		print('Acquisition mode set to continuous...')

		#  Begin acquiring images
		#
		#  *** NOTES ***
		#  What happens when the camera begins acquiring images depends on the
		#  acquisition mode. Single frame captures only a single image, multi
		#  frame catures a set number of images, and continuous captures a
		#  continuous stream of images. Because the example calls for the
		#  retrieval of 10 images, continuous mode has been set.
		#
		#  *** LATER ***
		#  Image acquisition must be ended when no more images are needed.
		cam.BeginAcquisition()

		print('Acquiring images...')

		#  Retrieve device serial number for filename
		#
		#  *** NOTES ***
		#  The device serial number is retrieved in order to keep cameras from
		#  overwriting one another. Grabbing image IDs could also accomplish
		#  this.
		device_serial_number = ''
		node_device_serial_number = PySpin.CStringPtr(nodemap_tldevice.GetNode('DeviceSerialNumber'))
		if PySpin.IsAvailable(node_device_serial_number) and PySpin.IsReadable(node_device_serial_number):
			device_serial_number = node_device_serial_number.GetValue()
			print('Device serial number retrieved as %s...' % device_serial_number)


		# make a figure
		#20190702 was here
		"""
		backend = matplotlib.get_backend()
		print("backend:", backend)
		myFig = plt.figure()
		winX = 100
		winY = 100
		winWidth = 500
		winHeight = 500
		thismanager = plt.get_current_fig_manager()
		thismanager.window.wm_geometry("+20+20")
		#plt.get_current_fig_manager().window.setGeometry(winX, winY, winWidth, winHeight)

		myFig.canvas.mpl_connect('close_event', myCloseCallback)
		myAxes = myFig.add_subplot(1,1,1)
		"""

		# 20190702 new
		playing = True

		# display 'paused' when paused
		font                   = cv2.FONT_HERSHEY_SIMPLEX
		#bottomLeftCornerOfText = (20,20)
		fontScale              = 2
		fontColor              = (0,255,255)
		lineType               = 3

		myText = ''

		#
		# save a file at an interval
		myPath = os.path.dirname(os.path.abspath(__file__))
		mySaveFilePath = os.path.join(myPath, 'oneimage.tif')

		saveIntervalSeconds = 1
		lastSaveTimeSeconds = 0

		# an opencv window to display the video
		cv2.namedWindow('myWindow',cv2.WINDOW_NORMAL)
		cv2.moveWindow('myWindow', 50, 20)
		cv2.resizeWindow('myWindow', 1024, 768)

		got_cv_width_height = False
		#20190702 end new

		lastSaveSeconds = 0
		mySaveInterval = 1 # seconds
		myFilename = 'c:\\Users\\cudmore\\Desktop\\bCamera\\tmp.tif'
		lastFrameSeconds = 0
		lastCameraFrameSeconds = 0

		#20190702 was here
		"""
		plt.pause(.1)
		"""

		# Retrieve, convert, and save images
		imgplot = None
		#for i in range(NUM_IMAGES):
		global myRunning
		while myRunning:
			try:

				#  Retrieve next received image
				#
				#  *** NOTES ***
				#  Capturing an image houses images on the camera buffer. Trying
				#  to capture an image that does not exist will hang the camera.
				#
				#  *** LATER ***
				#  Once an image from the buffer is saved and/or no longer
				#  needed, the image must be released in order to keep the
				#  buffer from filling up.
				image_result = cam.GetNextImage()

				#  Ensure image completion
				#
				#  *** NOTES ***
				#  Images can easily be checked for completion. This should be
				#  done whenever a complete image is expected or required.
				#  Further, check image status for a little more insight into
				#  why an image is incomplete.
				if image_result.IsIncomplete():
					print('Image incomplete with image status %d ...' % image_result.GetImageStatus())

				else:

					#  Print image information; height and width recorded in pixels
					#
					#  *** NOTES ***
					#  Images have quite a bit of available metadata including
					#  things such as CRC, image status, and offset values, to
					#  name a few.
					width = image_result.GetWidth()
					height = image_result.GetHeight()
					#print('Grabbed Image %d, width = %d, height = %d' % (i, width, height))

					#  Convert image to mono 8
					#
					#  *** NOTES ***
					#  Images can be converted between pixel formats by using
					#  the appropriate enumeration value. Unlike the original
					#  image, the converted one does not need to be released as
					#  it does not affect the camera buffer.
					#
					#  When converting images, color processing algorithm is an
					#  optional parameter.
					image_converted = image_result.Convert(PySpin.PixelFormat_Mono8, PySpin.HQ_LINEAR)

					# Create a unique filename
					'''
					if device_serial_number:
						filename = 'Acquisition-%s-%d.jpg' % (device_serial_number, i)
					else:  # if serial number is empty
						filename = 'Acquisition-%d.jpg' % i
					'''

					#  Save image
					#
					#  *** NOTES ***
					#  The standard practice of the examples is to use device
					#  serial numbers to keep images of one device from
					#  overwriting those of another.
					#image_converted.Save(filename)
					#print('Image saved at %s' % filename)

					# abb
					global image_data
					image_data = image_result.GetNDArray() # get as numpy array
					#print('image_data.shape:', image_data.shape)

					now = time.time()
					if now > lastSaveSeconds + mySaveInterval:
						# save the image
						print('saving image at:', now, mySaveFilePath)
						#image_converted.Save(myFilename)
						image_converted.Save(mySaveFilePath)
						
						lastSaveSeconds = now

					# 20190702 removed
					"""
					if imgplot is None:
						print('plotting first image')
						imgplot = myAxes.imshow(image_data, aspect='auto')
						imgplot.set_cmap('gray')
						plt.axis('off')
						lastFrameSeconds = now
					else:
						'''
						myAnimation = FuncAnimation(myFig, myUpdate, init_func=myInit, interval=30, blit=True) # interval (ms)
						'''
						if 1 and now-lastFrameSeconds > 0.1:
							#print('update')
							imgplot.set_data(image_data)
							myFig.canvas.draw()
							#myFig.canvas.draw_idle()
							#plt.pause(0.0001)
							lastFrameSeconds = now

					"""

					# 20190702 new
					if not got_cv_width_height:
						winWidth = width
						winHeight = height
						winHalfWidth = int(width * 0.5)
						winHalfHeight = int(height * 0.5)
						got_cv_width_height = True

					#if frame is not None:
					if True:
						"""
						cv2.putText(frame,
							myText,
							(winHalfWidth, winHalfHeight),
							font,
							fontScale,
							fontColor,
							lineType)
						"""
						if playing:
							cv2.imshow('myWindow',image_data)
					keyPress = cv2.waitKey(20) & 0xFF
					# 'q' to exit
					if keyPress == ord('q'):
						break
					# '+' or '=' to increase
					# '-' to decrease window size
					if keyPress == ord('-'):
						winWidth = int(winWidth * 0.8)
						winHeight = int(winHeight * 0.8)
						cv2.resizeWindow('myWindow', winWidth, winHeight)
					if keyPress in [ord('+'), ord('=')]:
						winWidth = int(winWidth * 1.2)
						winHeight = int(winHeight * 1.2)
						cv2.resizeWindow('myWindow', winWidth, winHeight)
					# 20190702 end new

					if now - lastCameraFrameSeconds > 0:
						print('fps:', 1 / (now - lastCameraFrameSeconds), 'width:', width, 'height:', height)
					lastCameraFrameSeconds = now

					#time.sleep(0.01)

					#plt.pause(.05)

					#print('i:', i)
					#plt.draw()
					#plt.show()


					#  Release image
					#
					#  *** NOTES ***
					#  Images retrieved directly from the camera (i.e. non-converted
					#  images) need to be released in order to keep from filling the
					#  buffer.
					image_result.Release()
					#print('')

			except PySpin.SpinnakerException as ex:
				print('Error: %s' % ex)
				#return False
			except KeyboardInterrupt as ex:
				#cam.EndAcquisition()
				return False

		#  End acquisition
		#
		#  *** NOTES ***
		#  Ending acquisition appropriately helps ensure that devices clean up
		#  properly and do not need to be power-cycled to maintain integrity.
		cam.EndAcquisition()

	except PySpin.SpinnakerException as ex:
		print('Error: %s' % ex)
		return False

	return result


def print_device_info(nodemap):
	"""
	This function prints the device information of the camera from the transport
	layer; please see NodeMapInfo example for more in-depth comments on printing
	device information from the nodemap.

	:param nodemap: Transport layer device nodemap.
	:type nodemap: INodeMap
	:returns: True if successful, False otherwise.
	:rtype: bool
	"""

	print('*** DEVICE INFORMATION ***\n')

	try:
		result = True
		node_device_information = PySpin.CCategoryPtr(nodemap.GetNode('DeviceInformation'))

		if PySpin.IsAvailable(node_device_information) and PySpin.IsReadable(node_device_information):
			features = node_device_information.GetFeatures()
			for feature in features:
				node_feature = PySpin.CValuePtr(feature)
				print('%s: %s' % (node_feature.GetName(),
								  node_feature.ToString() if PySpin.IsReadable(node_feature) else 'Node not readable'))

		else:
			print('Device control information not available.')

	except PySpin.SpinnakerException as ex:
		print('Error: %s' % ex)
		return False

	return result


def run_single_camera(cam):
	"""
	This function acts as the body of the example; please see NodeMapInfo example
	for more in-depth comments on setting up cameras.

	:param cam: Camera to run on.
	:type cam: CameraPtr
	:return: True if successful, False otherwise.
	:rtype: bool
	"""
	try:
		result = True

		# Retrieve TL device nodemap and print device information
		nodemap_tldevice = cam.GetTLDeviceNodeMap()

		result &= print_device_info(nodemap_tldevice)

		# Initialize camera
		cam.Init()

		# Retrieve GenICam nodemap
		nodemap = cam.GetNodeMap()

		# Acquire images
		result &= acquire_images(cam, nodemap, nodemap_tldevice)

		# Deinitialize camera
		cam.DeInit()

	except PySpin.SpinnakerException as ex:
		print('Error: %s' % ex)
		result = False

	return result


def main():
	"""
	Example entry point; please see Enumeration example for more in-depth
	comments on preparing and cleaning up the system.

	:return: True if successful, False otherwise.
	:rtype: bool
	"""

	# Since this application saves images in the current folder
	# we must ensure that we have permission to write to this folder.
	# If we do not have permission, fail right away.
	try:
		test_file = open('test.txt', 'w+')
	except IOError:
		print('Unable to write to current directory. Please check permissions.')
		input('Press Enter to exit...')
		return False

	test_file.close()
	os.remove(test_file.name)

	result = True

	# Retrieve singleton reference to system object
	system = PySpin.System.GetInstance()

	# Get current library version
	version = system.GetLibraryVersion()
	print('Library version: %d.%d.%d.%d' % (version.major, version.minor, version.type, version.build))

	# Retrieve list of cameras from the system
	cam_list = system.GetCameras()

	num_cameras = cam_list.GetSize()

	print('Number of cameras detected: %d' % num_cameras)

	# Finish if there are no cameras
	if num_cameras == 0:

		# Clear camera list before releasing system
		cam_list.Clear()

		# Release system instance
		system.ReleaseInstance()

		print('Not enough cameras!')
		input('Done! Press Enter to exit...')
		return False

	# Run example on each camera
	for i, cam in enumerate(cam_list):

		print('Running example for camera %d...' % i)

		result &= run_single_camera(cam)
		print('Camera %d example complete... \n' % i)

	# Release reference to camera
	# NOTE: Unlike the C++ examples, we cannot rely on pointer objects being automatically
	# cleaned up when going out of scope.
	# The usage of del is preferred to assigning the variable to None.
	del cam

	# Clear camera list before releasing system
	cam_list.Clear()

	# Release system instance
	system.ReleaseInstance()

	#input('Done! Press Enter to exit...')
	print('Done!!!')
	return result

if __name__ == '__main__':
	main()
