Skip to content

Commit

Permalink
Add utility for checking capture config.
Browse files Browse the repository at this point in the history
  • Loading branch information
ebezzam committed Nov 29, 2023
1 parent a630faf commit c7aa6f3
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 59 deletions.
3 changes: 2 additions & 1 deletion configs/capture.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ exp: 0.5
config_pause: 2
sensor_mode: "0" # {'off': 0, 'auto': 1, 'sunlight': 2, 'cloudy': 3, 'shade': 4, 'tungsten': 5, 'fluorescent': 6, 'incandescent': 7, 'flash': 8, 'horizon': 9}
iso: 100
awb_gains: null
# awb_gains: null # to use gains estimte by system
awb_gains: [1.9, 1.2]

# -- only one of below should be set
down: null # downsample from maximum resolution
Expand Down
59 changes: 58 additions & 1 deletion lensless/hardware/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import paramiko
from pprint import pprint
from paramiko.ssh_exception import AuthenticationException, BadHostKeyException, SSHException
from lensless.hardware.sensor import SensorOptions
from lensless.hardware.sensor import SensorOptions, sensor_dict, SensorParam
import cv2
from lensless.utils.image import print_image_info
from lensless.utils.io import load_image
Expand All @@ -17,6 +17,63 @@
logging.getLogger("paramiko").setLevel(logging.WARNING)


def check_capture_config(config):

sensor = config.sensor
nbits_bayer = config.nbits_bayer
exp = config.exp
rgb = config.rgb
gray = config.gray
legacy = config.legacy
down = config.down
res = config.res
nbits_out = config.nbits_out
awb_gains = config.awb_gains

assert sensor in SensorOptions.values(), f"Sensor must be one of {SensorOptions.values()}"

assert res is None or down is None, "Cannot specify both res and down"
if res is not None:
assert len(res) == 2, "res must be a tuple of length 2"

assert rgb is False or gray is False, "Cannot set both rgb and gray"

supported_bit_depth = sensor_dict[sensor][SensorParam.BIT_DEPTH]
assert (
nbits_out in supported_bit_depth
), f"nbits_out must be one of {supported_bit_depth} for sensor {sensor}"
assert nbits_bayer in [8, 16], "nbits_bayer must be 8 or 16"
if nbits_bayer == 16:
# TODO may not work if there are multiple bit depths above 8
nbits_measured = max(supported_bit_depth)
assert nbits_measured > 8
else:
nbits_measured = 8

if SensorParam.BLACK_LEVEL in sensor_dict[sensor]:
black_level = sensor_dict[sensor][SensorParam.BLACK_LEVEL] * (2**nbits_measured - 1)
else:
black_level = 0
if SensorParam.CCM_MATRIX in sensor_dict[sensor]:
ccm = sensor_dict[sensor][SensorParam.CCM_MATRIX]
else:
ccm = None

# https://www.raspberrypi.com/documentation/accessories/camera.html#hardware-specification
sensor_param = sensor_dict[sensor]
assert exp <= sensor_param[SensorParam.MAX_EXPOSURE]
assert exp >= sensor_param[SensorParam.MIN_EXPOSURE]

if awb_gains is not None:
assert len(awb_gains) == 2, "awb_gains must be a tuple of length 2"

if sensor == SensorOptions.RPI_GS.value:
assert not legacy, "Legacy capture software not supported for Global Shutter sensor"

# return sensor parameters
return black_level, ccm, supported_bit_depth, nbits_measured


def capture(
rpi_username,
rpi_hostname,
Expand Down
19 changes: 9 additions & 10 deletions lensless/utils/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,25 +270,24 @@ def bayer2rgb_cc(

if ccm is None:
ccm = np.eye(3)
ccm = np.array(ccm).astype(np.float32)
ccm = ccm.copy().astype(np.float32)

# demosaic Bayer data
img = cv2.cvtColor(img, cv2.COLOR_BayerRG2RGB)
img = cv2.cvtColor(img, cv2.COLOR_BayerRG2RGB).astype(np.float32)

# correction
img = img - black_level
img = np.clip(img, 0, 2**nbits - 1).astype(np.float32)

img -= black_level
if red_gain:
img[:, :, 0] *= red_gain
if blue_gain:
img[:, :, 2] *= blue_gain
img = img / (2**nbits - 1 - black_level)
np.clip(img, 0, 2**nbits - 1, out=img)
img /= 2**nbits - 1 - black_level
img[img > 1] = 1
img_re = img.reshape(-1, 3, order="F")
img_re = img_re @ ccm.T
img = img_re.reshape(img.shape, order="F")
# img = (img.reshape(-1, 3, order="F") @ ccm.T).reshape(img.shape, order="F")
# img_re = img.reshape(-1, 3, order="F")
# img_re = img_re @ ccm.T
# img = img_re.reshape(img.shape, order="F")
img = (img.reshape(-1, 3, order="F") @ ccm.T).reshape(img.shape, order="F")
img[img < 0] = 0
img[img > 1] = 1
return (img * (2**nbits_out - 1)).astype(dtype)
Expand Down
53 changes: 6 additions & 47 deletions scripts/measure/on_device_capture.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@
import numpy as np
from time import sleep
from PIL import Image
from lensless.hardware.utils import get_distro
from lensless.hardware.utils import get_distro, check_capture_config
from lensless.utils.image import bayer2rgb_cc, rgb2gray, resize
from lensless.hardware.constants import RPI_HQ_CAMERA_CCM_MATRIX, RPI_HQ_CAMERA_BLACK_LEVEL
from lensless.hardware.sensor import SensorOptions, sensor_dict, SensorParam
from fractions import Fraction
import time
Expand All @@ -56,13 +55,14 @@
@hydra.main(version_base=None, config_path="../../configs", config_name="capture")
def capture(config):

sensor = config.sensor
black_level, ccm, supported_bit_depth, nbits_measured = check_capture_config(config)

bayer = config.bayer
nbits_bayer = config.nbits_bayer
fn = config.fn
exp = config.exp
config_pause = config.config_pause
sensor_mode = config.sensor_mode
sensor_mode = int(config.sensor_mode)
rgb = config.rgb
gray = config.gray
iso = config.iso
Expand All @@ -71,44 +71,9 @@ def capture(config):
res = config.res
nbits_out = config.nbits_out

assert sensor in SensorOptions.values(), f"Sensor must be one of {SensorOptions.values()}"

assert res is None or down is None, "Cannot specify both res and down"
assert rgb is False or gray is False, "Cannot set both rgb and gray"

supported_bit_depth = sensor_dict[sensor][SensorParam.BIT_DEPTH]
assert (
nbits_out in supported_bit_depth
), f"nbits_out must be one of {supported_bit_depth} for sensor {sensor}"
assert nbits_bayer in [8, 16], "nbits_bayer must be 8 or 16"
if nbits_bayer == 16:
# TODO may not work if there are multiple bit depths above 8
nbits_measured = max(supported_bit_depth)
assert nbits_measured > 8
else:
nbits_measured = 8

if SensorParam.BLACK_LEVEL in sensor_dict[sensor]:
black_level = sensor_dict[sensor][SensorParam.BLACK_LEVEL] * (2**nbits_measured - 1)
else:
black_level = 0
if SensorParam.CCM_MATRIX in sensor_dict[sensor]:
ccm = sensor_dict[sensor][SensorParam.CCM_MATRIX]
else:
ccm = None

# https://www.raspberrypi.com/documentation/accessories/camera.html#hardware-specification
sensor_param = sensor_dict[sensor]
assert exp <= sensor_param[SensorParam.MAX_EXPOSURE]
assert exp >= sensor_param[SensorParam.MIN_EXPOSURE]
sensor_mode = int(sensor_mode)

distro = get_distro()
print("RPi distribution : {}".format(distro))

if sensor == SensorOptions.RPI_GS.value:
assert not legacy

if "bullseye" in distro and not legacy:
# TODO : grayscale and downsample
assert not rgb
Expand Down Expand Up @@ -161,9 +126,7 @@ def capture(config):
fn += ".png"

max_res = picam2.camera_properties["PixelArraySize"]
if res:
assert len(res) == 2
else:
if res is None:
res = np.array(max_res)
if down is not None:
res = (np.array(res) / down).astype(int)
Expand All @@ -184,7 +147,6 @@ def capture(config):
"AnalogueGain": 1.0,
}
if config.awb_gains is not None:
assert len(config.awb_gains) == 2
new_controls["ColourGains"] = tuple(config.awb_gains)
picam2.set_controls(new_controls)

Expand Down Expand Up @@ -281,9 +243,7 @@ def capture(config):
from picamerax import PiCamera

camera = PiCamera()
if res:
assert len(res) == 2
else:
if res is None:
res = np.array(camera.MAX_RESOLUTION)
if down is not None:
res = (np.array(res) / down).astype(int)
Expand All @@ -295,7 +255,6 @@ def capture(config):
time.sleep(config.config_pause)

if config.awb_gains is not None:
assert len(config.awb_gains) == 2
g = (Fraction(config.awb_gains[0]), Fraction(config.awb_gains[1]))
g = tuple(g)
camera.awb_mode = "off"
Expand Down

0 comments on commit c7aa6f3

Please sign in to comment.