<reponame>IanTBlack/OSFC<filename>core/camera.py
from datetime import datetime, timezone
import numpy as np
import os
from osfc.core.common import *
import picamera
import time

"""
--width
--height
-t (how long program runs (for previews) max of 5000)
-o output filename
--rawfull 1 or 0, force use full res
- n no preview
-p preview 0,0,0,0 size of window and resolution
--hflip 1,0
--vflip
--rotation 0 or 180
--roi  (0,0,0,0)
--shutter
--analoggain
-metering
-exposure
-ev
-awb
--awbgains
--flush
--wrap
--brightness
--contrast
--saturation
--sharpness
--framerate
--denoise
--

"""
import os
import osfc.core.common as common


class LIBCAMERA():
    def __init__(self, verbose=False):
        self.verbose = verbose
        self.payload = {}

    def set_width(self, pixels=1920):
        self.payload['--width'] = pixels

    width = property(fset=set_width)

    def set_height(self, pixels=1080):
        self.payload['--height'] = pixels

    height = property(fset=set_height)

    def set_hflip(self, boolean):
        if boolean is True:
            value = 1
        elif boolean is False:
            value = 0
        else:
            raise ValueError("hflip selection must be true or false.")
        self.payload['--hflip'] = int(value)

    hflip = property(fset=set_hflip)

    def set_vflip(self, boolean):
        if boolean is True:
            value = 1
        elif boolean is False:
            value = 0
        else:
            raise ValueError("vflip selection must be true or false.")
        self.payload['--vflip'] = int(value)

    vflip = property(fset=set_vflip)

    def capture(self, filename_or_filepath=None, fmt='.png'):
        if filename_or_filepath is None:
            fp = common.IMG_LOCATION
        self.payload['-o'] = filename_or_filepath + fmt
        cmd = "libcamera-still"  # Base libcamera app for capturing images.
        for option, arg in self.payload:
            cmd = " ".join([cmd, option, arg])
        os.system(cmd)
        if self.verbose is True:
            msg = "Captured file {} using command {}."
            print(msg.format(filename_or_filepath, cmd))

    def record(self, filename_or_filepath=None, fmt='.h264'):
        self.payload['-o'] = filename_or_filepath + fmt
        cmd = "libcamera-vid"  # Base libcamera app for capturing images.
        for option, arg in self.payload:
            cmd = " ".join([cmd, option, arg])
        os.system(cmd)
        if self.verbose is True:
            msg = "Captured file {} using command {}."
            print(msg.format(filename_or_filepath, cmd))


class PICAMERA():
    def __init__(self):
        self.settings = {}
        self.video_quality = 1
        self.video_bitrate = 25000000

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stop()

    def start(self):
        self.camera = picamera.PiCamera()
        self.resolution = common.RPi_HQ_MAX_RESOLUTION
        time.sleep(3)

    def stop(self):
        self.camera.close()
        if self.camera.closed is False:
            raise ConnectionError("There was an issue exiting the camera.")

    def _compute_rgb_array_size(self, width_height_tuple):
        width, height = width_height_tuple
        self.rgb_w = int(np.ceil(width/32)*32)
        self.rgb_h = int(np.ceil(height/16)*16)

    def raw_capture(self, fmt='rgb', resize=False):
        if self.camera.recording is True:
            use_video_port = True
        elif self.camera.recording is False:
            use_video_port = False
        holder = np.empty((self.rgb_w * self.rgb_h * 3,), dtype=np.uint8)
        dt = common.dt()  # Get a datetime object using osfc.core.common.
        self.camera.capture(holder, fmt, resize=resize,
                            use_video_port=use_video_port)
        raw = holder.reshape((self.rgb_w, self.rgb_h, 3))
        width, height = self.resolution
        raw = raw[:width, :height, :]
        return (raw, dt)

    def record(self, seconds):
        self.start_recording()
        self.camera.wait_recording(seconds)
        self.stop_recording()

    def start_recording(self):
        filename = common.iso8601() + '.h264'
        directory = common.VID_LOCATION
        filepath = os.path.join(directory, filename)
        self.camera.start_recording(filepath, bitrate=self.video_bitrate,
                                    quality=self.video_quality)
        self.camera.wait_recording(0.2)

    def stop_recording(self):
        self.camera.stop_recording()

    def _get_settings(self):
        self.resolution
        self.iso
        self.brightness

    def save_settings(self):
        self._get_settings()
        fmt = common.ISO8601_safe
        now = datetime.now(timezone.utc).strftime(fmt)
        with open("camera_{}.cfg".format(now), 'a') as cfg:
            for setting in self.settings:
                cfg.write("{}={}\n".format(setting, self.settings[setting]))

    def load_settings(self, filename):
        self.loaded_settings = {}
        with open('camera.cfg', 'r') as cfg:
            lines = cfg.readlines()
            for line in lines:
                setting, value = line.split('=')
                if "," in value:
                    params = value.count(",")+1
                    values = []
                    for i in range(params):
                        try:
                            v = float(value.split(',')[i])
                        except:
                            v = str(value.split(',')[i])
                        values.append(v)
                    values = tuple(values)
                self.loaded_settings[setting] = values

    def _get_res(self):
        return self.camera.resolution

    def _set_res(self, width_height_tuple):
        width = width_height_tuple[0]
        height = width_height_tuple[1]
        if width < 0 or width > 4056:
            raise ValueError('Width must be an integer between 0-4056.')
        if height < 0 or height > 3040:
            raise ValueError('Height must be an integer between 0-3040.')
        self._compute_rgb_array_size(width_height_tuple)
        self.camera.resolution = width_height_tuple
        self.settings['resolution'] = '{},{}'.format(width, height)

    resolution = property(_get_res, _set_res)

    def _get_iso(self):
        return self.camera.iso

    def _set_iso(self, value):
        if value < 0 or value > 1600:
            raise ValueError("ISO value must be between 0 and 1600.")
        self.camera.iso = value
        self.settings["iso"] = value

    iso = property(_get_iso, _set_iso)

    def _get_brightness(self):
        return self.camera.brightness

    def _set_brightness(self, value):
        if value < 0 or value > 100:
            raise ValueError("Brightness value must be between 0 and 100.")
        self.camera.brightness = value
        self.settings['brightness'] = value

    brightness = property(_get_brightness, _set_brightness)

    def _get_contrast(self):
        return self.camera.contrast

    def _set_contrast(self, value):
        if value < -100 or value > 100:
            raise ValueError("Contrast value must be between - 100 and 100.")
        self.camera.contrast = value
        self.settings['contrast'] = value

    contrast = property(_get_contrast, _set_contrast)

    def _get_exp_comp(self):
        return self.camera.exposure_compensation

    def _set_exp_comp(self, value):
        if value < -25 or value > 25:
            raise ValueError("""Exposure Compensation value
                             must be between -25 and 25.""")
        self.camera.exposure_compensation = value
        self.settings['exposure_compensation'] = value

    exposure_compensation = property(_get_exp_comp, _set_exp_comp)

    def _get_exp_mode(self):
        return self.camera.exposure_mode

    def _set_exp_mode(self, mode='backlight'):
        modes = ['off', 'auto', 'night', 'nightpreview', 'backlight',
                 'spotlight', 'sports', 'snow', 'beach', 'verylong',
                 'fixedfps', 'antishake', 'fireworks']
        if mode not in modes:
            raise ValueError('Given mode is not accepted by picamera.')
        self.camera.exposure_mode = mode
        self.settings['exposure_mode'] = mode

    exposure_mode = property(_get_exp_mode, _set_exp_mode)

    def _get_flash_mode(self):
        return self.camera.flash_mode

    def _set_flash_mode(self, mode):
        modes = ['off', 'auto', 'on', 'redeye', 'fillin', 'torch']
        if mode not in modes:
            raise ValueError('Given mode is not accepted by picamera.')
        self.camera.flash_mode = mode
        self.settings['flash_mode'] = mode

    flash_mode = property(_get_flash_mode, _set_flash_mode)

    def _get_hflip(self):
        return self.camera.hflip

    def _set_hflip(self, state=False):
        self.camera.hflip = state
        self.settings['hflip'] = state
    hflip = property(_get_hflip, _set_hflip)

    def _get_meter_mode(self):
        return self.camera.meter_mode

    def _set_meter_mode(self, mode="backlight"):
        modes = ["average", "spot", "backlit", "matrix"]
        if mode not in modes:
            raise ValueError('Given mode is not accepted by picamera.')
        self.camera.meter_mode = mode
        self.settings['meter_mode'] = mode

    meter_mode = property(_get_meter_mode, _set_meter_mode)

    def _get_saturation(self):
        return self.camera.saturation

    def _set_saturation(self, value):
        if value < -100 or value > 100:
            raise ValueError("Saturation value must be between -100 and 100.")
        self.camera.saturation = value
        self.settings['saturation'] = value

    saturation = property(_get_saturation, _set_saturation)

    def _get_sharpness(self):
        return self.camera.sharpness

    def _set_sharpness(self, value):
        if value < -100 or value > 100:
            raise ValueError("Sharpness value must be between -100 and 100.")
        self.camera.sharpness = value
        self.settings['sharpness'] = value

    sharpness = property(_get_sharpness, _set_sharpness)

    def _get_shutter_speed(self):
        return self.camera.shutter_speed

    def _set_shutter_speed(self, value):
        if value < 0:
            raise ValueError("Shutter speed must be positive.")
        self.camera.shutter_speed = value
        self.settings['shutter_speed'] = value

    shutter_speed = property(_get_shutter_speed, _set_shutter_speed)

    def _get_vflip(self):
        return self.camera.vflip

    def _set_vflip(self, state=False):
        self.camera.vflip = state
        self.settings['vflip'] = state

    vflip = property(_get_vflip, _set_vflip)

    def _get_zoom(self):
        return self.camera.zoom

    def _set_zoom(self, zoom_tuple=(0, 0, 1.0, 1.0)):
        self.camera.zoom = zoom_tuple
        self.settings['zoom'] = zoom_tuple

    zoom = property(_get_zoom, _set_zoom)

    def _get_framerate(self):
        return self.camera.framerate

    def _set_framerate(self, value=15):
        if value < 1 or value > 90:
            raise ValueError("Supplied framerate is not between 1 and 90.")
        self.camera.framerate = value
        self.settings['framerate'] = value

    framerate = property(_get_framerate, _set_framerate)

    def _get_image_denoise(self):
        return self.camera.image_denoise

    def _set_image_denoise(self, state=True):
        self.camera.image_denoise = state
        self.settings['image_denoise'] = state

    image_denoise = property(_get_image_denoise, _set_image_denoise)

    def _get_video_denoise(self):
        return self.camera.image_denoise

    def _set_video_denoise(self, state=True):
        self.camera.video_denoise = state
        self.settings['video_denoise'] = state

    video_denoise = property(_get_video_denoise, _set_video_denoise)

    def _get_video_stabilization(self):
        return self.camera.video_stabilization

    def _set_video_stabilization(self, state=False):
        self.camera.video_stabilization = state
        self.settings['video_stabilization'] = state

from datetime import datetime, timezone
import numpy as np
import os
from osfc.core.common import *
import picamera
import time

"""
--width
--height
-t (how long program runs (for previews) max of 5000)
-o output filename
--rawfull 1 or 0, force use full res
- n no preview
-p preview 0,0,0,0 size of window and resolution
--hflip 1,0
--vflip
--rotation 0 or 180
--roi  (0,0,0,0)
--shutter
--analoggain
-metering
-exposure
-ev
-awb
--awbgains
--flush
--wrap
--brightness
--contrast
--saturation
--sharpness
--framerate
--denoise
--

"""
import os
import osfc.core.common as common


class LIBCAMERA():
    def __init__(self, verbose=False):
        self.verbose = verbose
        self.payload = {}

    def set_width(self, pixels=1920):
        self.payload['--width'] = pixels

    width = property(fset=set_width)

    def set_height(self, pixels=1080):
        self.payload['--height'] = pixels

    height = property(fset=set_height)

    def set_hflip(self, boolean):
        if boolean is True:
            value = 1
        elif boolean is False:
            value = 0
        else:
            raise ValueError("hflip selection must be true or false.")
        self.payload['--hflip'] = int(value)

    hflip = property(fset=set_hflip)

    def set_vflip(self, boolean):
        if boolean is True:
            value = 1
        elif boolean is False:
            value = 0
        else:
            raise ValueError("vflip selection must be true or false.")
        self.payload['--vflip'] = int(value)

    vflip = property(fset=set_vflip)

    def capture(self, filename_or_filepath=None, fmt='.png'):
        if filename_or_filepath is None:
            fp = common.IMG_LOCATION
        self.payload['-o'] = filename_or_filepath + fmt
        cmd = "libcamera-still"  # Base libcamera app for capturing images.
        for option, arg in self.payload:
            cmd = " ".join([cmd, option, arg])
        os.system(cmd)
        if self.verbose is True:
            msg = "Captured file {} using command {}."
            print(msg.format(filename_or_filepath, cmd))

    def record(self, filename_or_filepath=None, fmt='.h264'):
        self.payload['-o'] = filename_or_filepath + fmt
        cmd = "libcamera-vid"  # Base libcamera app for capturing images.
        for option, arg in self.payload:
            cmd = " ".join([cmd, option, arg])
        os.system(cmd)
        if self.verbose is True:
            msg = "Captured file {} using command {}."
            print(msg.format(filename_or_filepath, cmd))


class PICAMERA():
    def __init__(self):
        self.settings = {}
        self.video_quality = 1
        self.video_bitrate = 25000000

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stop()

    def start(self):
        self.camera = picamera.PiCamera()
        self.resolution = common.RPi_HQ_MAX_RESOLUTION
        time.sleep(3)

    def stop(self):
        self.camera.close()
        if self.camera.closed is False:
            raise ConnectionError("There was an issue exiting the camera.")

    def _compute_rgb_array_size(self, width_height_tuple):
        width, height = width_height_tuple
        self.rgb_w = int(np.ceil(width/32)*32)
        self.rgb_h = int(np.ceil(height/16)*16)

    def raw_capture(self, fmt='rgb', resize=False):
        if self.camera.recording is True:
            use_video_port = True
        elif self.camera.recording is False:
            use_video_port = False
        holder = np.empty((self.rgb_w * self.rgb_h * 3,), dtype=np.uint8)
        dt = common.dt()  # Get a datetime object using osfc.core.common.
        self.camera.capture(holder, fmt, resize=resize,
                            use_video_port=use_video_port)
        raw = holder.reshape((self.rgb_w, self.rgb_h, 3))
        width, height = self.resolution
        raw = raw[:width, :height, :]
        return (raw, dt)

    def record(self, seconds):
        self.start_recording()
        self.camera.wait_recording(seconds)
        self.stop_recording()

    def start_recording(self):
        filename = common.iso8601() + '.h264'
        directory = common.VID_LOCATION
        filepath = os.path.join(directory, filename)
        self.camera.start_recording(filepath, bitrate=self.video_bitrate,
                                    quality=self.video_quality)
        self.camera.wait_recording(0.2)

    def stop_recording(self):
        self.camera.stop_recording()

    def _get_settings(self):
        self.resolution
        self.iso
        self.brightness

    def save_settings(self):
        self._get_settings()
        fmt = common.ISO8601_safe
        now = datetime.now(timezone.utc).strftime(fmt)
        with open("camera_{}.cfg".format(now), 'a') as cfg:
            for setting in self.settings:
                cfg.write("{}={}\n".format(setting, self.settings[setting]))

    def load_settings(self, filename):
        self.loaded_settings = {}
        with open('camera.cfg', 'r') as cfg:
            lines = cfg.readlines()
            for line in lines:
                setting, value = line.split('=')
                if "," in value:
                    params = value.count(",")+1
                    values = []
                    for i in range(params):
                        try:
                            v = float(value.split(',')[i])
                        except:
                            v = str(value.split(',')[i])
                        values.append(v)
                    values = tuple(values)
                self.loaded_settings[setting] = values

    def _get_res(self):
        return self.camera.resolution

    def _set_res(self, width_height_tuple):
        width = width_height_tuple[0]
        height = width_height_tuple[1]
        if width < 0 or width > 4056:
            raise ValueError('Width must be an integer between 0-4056.')
        if height < 0 or height > 3040:
            raise ValueError('Height must be an integer between 0-3040.')
        self._compute_rgb_array_size(width_height_tuple)
        self.camera.resolution = width_height_tuple
        self.settings['resolution'] = '{},{}'.format(width, height)

    resolution = property(_get_res, _set_res)

    def _get_iso(self):
        return self.camera.iso

    def _set_iso(self, value):
        if value < 0 or value > 1600:
            raise ValueError("ISO value must be between 0 and 1600.")
        self.camera.iso = value
        self.settings["iso"] = value

    iso = property(_get_iso, _set_iso)

    def _get_brightness(self):
        return self.camera.brightness

    def _set_brightness(self, value):
        if value < 0 or value > 100:
            raise ValueError("Brightness value must be between 0 and 100.")
        self.camera.brightness = value
        self.settings['brightness'] = value

    brightness = property(_get_brightness, _set_brightness)

    def _get_contrast(self):
        return self.camera.contrast

    def _set_contrast(self, value):
        if value < -100 or value > 100:
            raise ValueError("Contrast value must be between - 100 and 100.")
        self.camera.contrast = value
        self.settings['contrast'] = value

    contrast = property(_get_contrast, _set_contrast)

    def _get_exp_comp(self):
        return self.camera.exposure_compensation

    def _set_exp_comp(self, value):
        if value < -25 or value > 25:
            raise ValueError("""Exposure Compensation value
                             must be between -25 and 25.""")
        self.camera.exposure_compensation = value
        self.settings['exposure_compensation'] = value

    exposure_compensation = property(_get_exp_comp, _set_exp_comp)

    def _get_exp_mode(self):
        return self.camera.exposure_mode

    def _set_exp_mode(self, mode='backlight'):
        modes = ['off', 'auto', 'night', 'nightpreview', 'backlight',
                 'spotlight', 'sports', 'snow', 'beach', 'verylong',
                 'fixedfps', 'antishake', 'fireworks']
        if mode not in modes:
            raise ValueError('Given mode is not accepted by picamera.')
        self.camera.exposure_mode = mode
        self.settings['exposure_mode'] = mode

    exposure_mode = property(_get_exp_mode, _set_exp_mode)

    def _get_flash_mode(self):
        return self.camera.flash_mode

    def _set_flash_mode(self, mode):
        modes = ['off', 'auto', 'on', 'redeye', 'fillin', 'torch']
        if mode not in modes:
            raise ValueError('Given mode is not accepted by picamera.')
        self.camera.flash_mode = mode
        self.settings['flash_mode'] = mode

    flash_mode = property(_get_flash_mode, _set_flash_mode)

    def _get_hflip(self):
        return self.camera.hflip

    def _set_hflip(self, state=False):
        self.camera.hflip = state
        self.settings['hflip'] = state
    hflip = property(_get_hflip, _set_hflip)

    def _get_meter_mode(self):
        return self.camera.meter_mode

    def _set_meter_mode(self, mode="backlight"):
        modes = ["average", "spot", "backlit", "matrix"]
        if mode not in modes:
            raise ValueError('Given mode is not accepted by picamera.')
        self.camera.meter_mode = mode
        self.settings['meter_mode'] = mode

    meter_mode = property(_get_meter_mode, _set_meter_mode)

    def _get_saturation(self):
        return self.camera.saturation

    def _set_saturation(self, value):
        if value < -100 or value > 100:
            raise ValueError("Saturation value must be between -100 and 100.")
        self.camera.saturation = value
        self.settings['saturation'] = value

    saturation = property(_get_saturation, _set_saturation)

    def _get_sharpness(self):
        return self.camera.sharpness

    def _set_sharpness(self, value):
        if value < -100 or value > 100:
            raise ValueError("Sharpness value must be between -100 and 100.")
        self.camera.sharpness = value
        self.settings['sharpness'] = value

    sharpness = property(_get_sharpness, _set_sharpness)

    def _get_shutter_speed(self):
        return self.camera.shutter_speed

    def _set_shutter_speed(self, value):
        if value < 0:
            raise ValueError("Shutter speed must be positive.")
        self.camera.shutter_speed = value
        self.settings['shutter_speed'] = value

    shutter_speed = property(_get_shutter_speed, _set_shutter_speed)

    def _get_vflip(self):
        return self.camera.vflip

    def _set_vflip(self, state=False):
        self.camera.vflip = state
        self.settings['vflip'] = state

    vflip = property(_get_vflip, _set_vflip)

    def _get_zoom(self):
        return self.camera.zoom

    def _set_zoom(self, zoom_tuple=(0, 0, 1.0, 1.0)):
        self.camera.zoom = zoom_tuple
        self.settings['zoom'] = zoom_tuple

    zoom = property(_get_zoom, _set_zoom)

    def _get_framerate(self):
        return self.camera.framerate

    def _set_framerate(self, value=15):
        if value < 1 or value > 90:
            raise ValueError("Supplied framerate is not between 1 and 90.")
        self.camera.framerate = value
        self.settings['framerate'] = value

    framerate = property(_get_framerate, _set_framerate)

    def _get_image_denoise(self):
        return self.camera.image_denoise

    def _set_image_denoise(self, state=True):
        self.camera.image_denoise = state
        self.settings['image_denoise'] = state

    image_denoise = property(_get_image_denoise, _set_image_denoise)

    def _get_video_denoise(self):
        return self.camera.image_denoise

    def _set_video_denoise(self, state=True):
        self.camera.video_denoise = state
        self.settings['video_denoise'] = state

    video_denoise = property(_get_video_denoise, _set_video_denoise)

    def _get_video_stabilization(self):
        return self.camera.video_stabilization

    def _set_video_stabilization(self, state=False):
        self.camera.video_stabilization = state
        self.settings['video_stabilization'] = state
--width --height -t (how long program runs (for previews) max of 5000) -o output filename --rawfull 1 or 0, force use full res - n no preview -p preview 0,0,0,0 size of window and resolution --hflip 1,0 --vflip --rotation 0 or 180 --roi  (0,0,0,0) --shutter --analoggain -metering -exposure -ev -awb --awbgains --flush --wrap --brightness --contrast --saturation --sharpness --framerate --denoise -- # Base libcamera app for capturing images. # Base libcamera app for capturing images. # Get a datetime object using osfc.core.common. Exposure Compensation value                              must be between -25 and 25.