diff --git a/preview.py b/preview.py new file mode 100755 index 0000000..316743e --- /dev/null +++ b/preview.py @@ -0,0 +1,486 @@ +#!/usr/bin/env python3 +import sys +import os +import numpy as np +import cv2 +import time +import ctypes +import multiprocessing +from astropy.coordinates import EarthLocation +from astropy.time import Time +from astropy.io import fits +import astropy.units as u +from stvid.utils import get_sunset_and_sunrise +import logging +import configparser +import argparse +import zwoasi as asi + + +# Capture images from pi +def capture_pi(image_queue, z1, t1, z2, t2, nx, ny, nz, tend, device_id, live, cfg): + from picamerax.array import PiRGBArray + from picamerax import PiCamera + # Intialization + first = True + slow_CPU = False + + # Initialize cv2 device + camera = PiCamera(sensor_mode=2) + camera.resolution = (nx, ny) + # Turn off any thing automatic. + camera.exposure_mode = 'off' + camera.awb_mode = 'off' + # ISO needs to be 0 otherwise analog and digital gain won't work. + camera.iso = 0 + # set the camea settings + camera.framerate = cfg.getfloat(camera_type, 'framerate') + camera.awb_gains = (cfg.getfloat(camera_type, 'awb_gain_red'), cfg.getfloat(camera_type, 'awb_gain_blue')) + camera.analog_gain = cfg.getfloat(camera_type, 'analog_gain') + camera.digital_gain = cfg.getfloat(camera_type, 'digital_gain') + camera.shutter_speed = cfg.getint(camera_type, 'exposure') + + rawCapture = PiRGBArray(camera, size=(nx, ny)) + # allow the camera to warmup + time.sleep(0.1) + + try: + # Loop until reaching end time + while float(time.time()) < tend: + # Wait for available capture buffer to become available + if (image_queue.qsize() > 1): + logger.warning("Acquiring data faster than your CPU can process") + slow_CPU = True + while (image_queue.qsize() > 1): + time.sleep(0.1) + if slow_CPU: + lost_video = time.time() - t + logger.info("Waited %.3fs for available capture buffer" % lost_video) + slow_CPU = False + + # Get frames + i = 0 + for frameA in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True): + + # Store start time + t0 = float(time.time()) + # grab the raw NumPy array representing the image, then initialize the timestamp + frame = frameA.array + + # Compute mid time + t = (float(time.time())+t0)/2.0 + + # Skip lost frames + if frame is not None: + # Convert image to grayscale + z = np.asarray(cv2.cvtColor( + frame, cv2.COLOR_BGR2GRAY)).astype(np.uint8) + # optionally rotate the frame by 2 * 90 degrees. + # z = np.rot90(z, 2) + + # Display Frame + if live is True: + cv2.imshow("Capture", z) + cv2.waitKey(1) + + # Store results + if first: + z1[i] = z + t1[i] = t + else: + z2[i] = z + t2[i] = t + + # clear the stream in preparation for the next frame + rawCapture.truncate(0) + # count up to nz frames, then break out of the for loop. + i += 1 + if i >= nz: + break + + if first: + buf = 1 + else: + buf = 2 + image_queue.put(buf) + logger.debug("Captured z%d" % buf) + + # Swap flag + first = not first + reason = "Session complete" + except KeyboardInterrupt: + print() + reason = "Keyboard interrupt" + except ValueError as e: + logger.error("%s" % e) + reason = "Wrong image dimensions? Fix nx, ny in config." + finally: + # End capture + logger.info("Capture: %s - Exiting" % reason) + camera.close() + + +# Capture images from cv2 +def capture_cv2(image_queue, z1, t1, z2, t2, nx, ny, nz, tend, device_id, live): + # Intialization + first = True + slow_CPU = False + + # Initialize cv2 device + device = cv2.VideoCapture(device_id) + + # Set properties + device.set(3, nx) + device.set(4, ny) + + try: + # Loop until reaching end time + while float(time.time()) < tend: + # Wait for available capture buffer to become available + if (image_queue.qsize() > 1): + logger.warning("Acquiring data faster than your CPU can process") + slow_CPU = True + while (image_queue.qsize() > 1): + time.sleep(0.1) + if slow_CPU: + lost_video = time.time() - t + logger.info("Waited %.3fs for available capture buffer" % lost_video) + slow_CPU = False + + # Get frames + for i in range(nz): + # Store start time + t0 = float(time.time()) + + # Get frame + res, frame = device.read() + + # Compute mid time + t = (float(time.time())+t0)/2.0 + + # Skip lost frames + if res is True: + # Convert image to grayscale + z = np.asarray(cv2.cvtColor( + frame, cv2.COLOR_BGR2GRAY)).astype(np.uint8) + + # Display Frame + if live is True: + cv2.imshow("Capture", z) + cv2.waitKey(1) + + # Store results + if first: + z1[i] = z + t1[i] = t + else: + z2[i] = z + t2[i] = t + + if first: + buf = 1 + else: + buf = 2 + image_queue.put(buf) + logger.debug("Captured z%d" % buf) + + # Swap flag + first = not first + reason = "Session complete" + except KeyboardInterrupt: + print() + reason = "Keyboard interrupt" + except ValueError as e: + logger.error("%s" % e) + reason = "Wrong image dimensions? Fix nx, ny in config." + finally: + # End capture + logger.info("Capture: %s - Exiting" % reason) + device.release() + + +# Capture images +def capture_asi(image_queue, z1, t1, z2, t2, nx, ny, nz, tend, device_id, live, cfg): + first = True # Array flag + slow_CPU = False # Performance issue flag + + + camera_type = "ASI" + gain = cfg.getint(camera_type, 'gain') + maxgain = cfg.getint(camera_type, 'maxgain') + autogain = cfg.getboolean(camera_type, 'autogain') + exposure = cfg.getint(camera_type, 'exposure') + binning = cfg.getint(camera_type, 'bin') + brightness = cfg.getint(camera_type, 'brightness') + bandwidth = cfg.getint(camera_type, 'bandwidth') + high_speed = cfg.getint(camera_type, 'high_speed') + hardware_bin = cfg.getint(camera_type, 'hardware_bin') + sdk = cfg.get(camera_type, 'sdk') + try: + software_bin = cfg.getint(camera_type, 'software_bin') + except configparser.Error: + software_bin = 0 + + # Initialize device + asi.init(sdk) + + num_cameras = asi.get_num_cameras() + if num_cameras == 0: + logger.error("No ZWOASI cameras found") + raise ValueError + sys.exit() + + cameras_found = asi.list_cameras() # Models names of the connected cameras + + if num_cameras == 1: + device_id = 0 + logger.info("Found one camera: %s" % cameras_found[0]) + else: + logger.info("Found %d ZWOASI cameras" % num_cameras) + for n in range(num_cameras): + logger.info(" %d: %s" % (n, cameras_found[n])) + logger.info("Using #%d: %s" % (device_id, cameras_found[device_id])) + + camera = asi.Camera(device_id) + camera_info = camera.get_camera_property() + logger.debug("ASI Camera info:") + for (key, value) in camera_info.items(): + logger.debug(" %s : %s" % (key,value)) + + camera.set_control_value(asi.ASI_BANDWIDTHOVERLOAD, bandwidth) + camera.disable_dark_subtract() + camera.set_control_value(asi.ASI_GAIN, gain, auto=autogain) + camera.set_control_value(asi.ASI_EXPOSURE, exposure, auto=False) + camera.set_control_value(asi.ASI_AUTO_MAX_GAIN, maxgain) + camera.set_control_value(asi.ASI_AUTO_MAX_BRIGHTNESS, 20) + camera.set_control_value(asi.ASI_WB_B, 99) + camera.set_control_value(asi.ASI_WB_R, 75) + camera.set_control_value(asi.ASI_GAMMA, 50) + camera.set_control_value(asi.ASI_BRIGHTNESS, brightness) + camera.set_control_value(asi.ASI_FLIP, 0) + try: + camera.set_control_value(asi.ASI_HIGH_SPEED_MODE, high_speed) + except: + pass + try: + camera.set_control_value(asi.ASI_HARDWARE_BIN, hardware_bin) + except: + pass + camera.set_roi(bins=binning) + camera.start_video_capture() + camera.set_image_type(asi.ASI_IMG_RAW8) + + try: + # Fix autogain + if autogain: + while True: + # Get frame + z = camera.capture_video_frame() + + # Break on no change in gain + settings = camera.get_control_values() + if gain == settings["Gain"]: + break + gain = settings["Gain"] + camera.set_control_value(asi.ASI_GAIN, gain, auto=autogain) + + # Loop until reaching end time + while float(time.time()) < tend: + # Wait for available capture buffer to become available + if (image_queue.qsize() > 1): + logger.warning("Acquiring data faster than your CPU can process") + slow_CPU = True + while (image_queue.qsize() > 1): + time.sleep(0.1) + if slow_CPU: + lost_video = time.time() - t + logger.info("Waited %.3fs for available capture buffer" % lost_video) + slow_CPU = False + + # Get settings + settings = camera.get_control_values() + gain = settings["Gain"] + temp = settings["Temperature"]/10.0 + logger.info("Capturing frame with gain %d, temperature %.1f" % (gain, temp)) + + # Set gain + if autogain: + camera.set_control_value(asi.ASI_GAIN, gain, auto=autogain) + + # Get frames + for i in range(nz): + # Store start time + t0 = float(time.time()) + + # Get frame + z = camera.capture_video_frame() + + # Apply software binning + if software_bin > 1: + my, mx = z.shape + z = cv2.resize(z, (mx // software_bin, my // software_bin)) + + # Compute mid time + t = (float(time.time())+t0)/2.0 + + # Display Frame + if live is True: + cv2.imshow("Capture", z) + cv2.waitKey(1) + + # Store results + if first: + z1[i] = z + t1[i] = t + else: + z2[i] = z + t2[i] = t + + if first: + buf = 1 + else: + buf = 2 + image_queue.put(buf) + logger.debug("Captured buffer %d (%dx%dx%d)" % (buf, nx, ny, nz)) + + # Swap flag + first = not first + reason = "Session complete" + except KeyboardInterrupt: + print() + reason = "Keyboard interrupt" + except ValueError as e: + logger.error("%s" % e) + reason = "Wrong image dimensions? Fix nx, ny in config." + except MemoryError as e: + logger.error("Capture: Memory error %s" % e) + finally: + # End capture + logger.info("Capture: %s - Exiting" % reason) + camera.stop_video_capture() + camera.close() + + +# Main function +if __name__ == '__main__': + + # Read commandline options + conf_parser = argparse.ArgumentParser(description='Preview' + + ' live video frames.') + conf_parser.add_argument('-c', '--conf_file', + help="Specify configuration file. If no file" + + " is specified 'configuration.ini' is used.", + metavar="FILE") + + args = conf_parser.parse_args() + + # Process commandline options and parse configuration + cfg = configparser.ConfigParser(inline_comment_prefixes=('#', ';')) + + conf_file = args.conf_file if args.conf_file else "configuration.ini" + result = cfg.read([conf_file]) + + if not result: + print("Could not read config file: %s\nExiting..." % conf_file) + sys.exit() + + # Setup logging + logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] " + + "[%(levelname)-5.5s] %(message)s") + logger = logging.getLogger() + + # Generate directory + path = os.path.abspath(cfg.get('Common', 'observations_path')) + if not os.path.exists(path): + try: + os.makedirs(path) + except PermissionError: + logger.error("Can not create observations_path: %s" % path) + sys.exit() + + fileHandler = logging.FileHandler(os.path.join(path, "preview.log")) + fileHandler.setFormatter(logFormatter) + logger.addHandler(fileHandler) + + consoleHandler = logging.StreamHandler(sys.stdout) + consoleHandler.setFormatter(logFormatter) + logger.addHandler(consoleHandler) + logger.setLevel(logging.DEBUG) + + logger.info("Using config: %s" % conf_file) + + # Testing mode + test = True + testing = True + test_duration = 3600 + logger.info("Preview duration: %ds" % test_duration) + + # Live mode + live = True + + # Get camera type + camera_type = cfg.get('Camera', 'camera_type') + + # Get device id + device_id = cfg.getint(camera_type, 'device_id') + + # Current time + tnow = Time.now() + + # Set location + loc = EarthLocation(lat=cfg.getfloat('Common', 'observer_lat')*u.deg, + lon=cfg.getfloat('Common', 'observer_lon')*u.deg, + height=cfg.getfloat('Common', 'observer_height')*u.m) + + tend = tnow + test_duration*u.s + + logger.info("Starting data acquisition") + logger.info("Acquisition will end after "+tend.isot) + + # Get settings + nx = cfg.getint(camera_type, 'nx') + ny = cfg.getint(camera_type, 'ny') + nz = cfg.getint(camera_type, 'nframes') + + # Initialize arrays + z1base = multiprocessing.Array(ctypes.c_uint8, nx*ny*nz) + z1 = np.ctypeslib.as_array(z1base.get_obj()).reshape(nz, ny, nx) + t1base = multiprocessing.Array(ctypes.c_double, nz) + t1 = np.ctypeslib.as_array(t1base.get_obj()) + z2base = multiprocessing.Array(ctypes.c_uint8, nx*ny*nz) + z2 = np.ctypeslib.as_array(z2base.get_obj()).reshape(nz, ny, nx) + t2base = multiprocessing.Array(ctypes.c_double, nz) + t2 = np.ctypeslib.as_array(t2base.get_obj()) + + image_queue = multiprocessing.Queue() + + # Set processes + if camera_type == "PI": + pcapture = multiprocessing.Process(target=capture_pi, + args=(image_queue, z1, t1, z2, t2, + nx, ny, nz, tend.unix, device_id, live, cfg)) + elif camera_type == "CV2": + pcapture = multiprocessing.Process(target=capture_cv2, + args=(image_queue, z1, t1, z2, t2, + nx, ny, nz, tend.unix, device_id, live)) + elif camera_type == "ASI": + pcapture = multiprocessing.Process(target=capture_asi, + args=(image_queue, z1, t1, z2, t2, + nx, ny, nz, tend.unix, device_id, live, cfg)) + + # Start + pcapture.start() + + # End + try: + pcapture.join() + except (KeyboardInterrupt, ValueError): + time.sleep(0.1) # Allow a little time for a graceful exit + except MemoryError as e: + logger.error("Memory error %s" % e) + finally: + pcapture.terminate() + + # Release device + if live is True: + cv2.destroyAllWindows()