Static type analysis
This commit is contained in:
parent
3aebb8bead
commit
7866ec0f47
63 changed files with 1825 additions and 2722 deletions
|
|
@ -1,14 +1,15 @@
|
|||
import logging
|
||||
from contextlib import contextmanager
|
||||
|
||||
# Type hinting
|
||||
from typing import Tuple
|
||||
|
||||
import picamerax
|
||||
from flask import abort
|
||||
from labthings import find_component
|
||||
from labthings.extensions import BaseExtension
|
||||
from labthings.views import ActionView
|
||||
|
||||
from openflexure_microscope.camera.base import BaseCamera
|
||||
from openflexure_microscope.microscope import Microscope
|
||||
|
||||
from .recalibrate_utils import (
|
||||
auto_expose_and_freeze_settings,
|
||||
flat_lens_shading_table,
|
||||
|
|
@ -17,7 +18,7 @@ from .recalibrate_utils import (
|
|||
|
||||
|
||||
@contextmanager
|
||||
def pause_stream(scamera, resolution: Tuple[int, int] = None):
|
||||
def pause_stream(scamera: BaseCamera):
|
||||
"""This context manager locks a streaming camera, and pauses the stream.
|
||||
|
||||
The stream is re-enabled, with the original resolution, once the with
|
||||
|
|
@ -28,20 +29,20 @@ def pause_stream(scamera, resolution: Tuple[int, int] = None):
|
|||
not scamera.record_active
|
||||
), "We can't pause the camera's video stream while a recording is in progress."
|
||||
streaming = scamera.stream_active
|
||||
old_resolution = scamera.camera.resolution
|
||||
old_resolution = scamera.stream_resolution
|
||||
if streaming:
|
||||
logging.info("Stopping stream in pause_stream context manager")
|
||||
scamera.stop_stream_recording(resolution=resolution)
|
||||
scamera.stop_stream()
|
||||
try:
|
||||
yield scamera
|
||||
finally:
|
||||
scamera.camera.resolution = old_resolution
|
||||
scamera.stream_resolution = old_resolution
|
||||
if streaming:
|
||||
logging.info("Restarting stream in pause_stream context manager")
|
||||
scamera.start_stream_recording()
|
||||
scamera.start_stream()
|
||||
|
||||
|
||||
def recalibrate(microscope):
|
||||
def recalibrate(microscope: Microscope):
|
||||
"""Reset the camera's settings.
|
||||
|
||||
This generates new gains, exposure time, and lens shading
|
||||
|
|
@ -49,11 +50,15 @@ def recalibrate(microscope):
|
|||
with a gray level of 230. It takes a little while to run.
|
||||
"""
|
||||
with pause_stream(microscope.camera) as scamera:
|
||||
auto_expose_and_freeze_settings(
|
||||
scamera.camera
|
||||
) # scamera.camera is the PiCamera object
|
||||
recalibrate_camera(scamera.camera)
|
||||
microscope.save_settings()
|
||||
if hasattr(scamera, "picamera"):
|
||||
picamera_obj: picamerax.PiCamera = getattr(scamera, "picamera")
|
||||
auto_expose_and_freeze_settings(picamera_obj)
|
||||
recalibrate_camera(picamera_obj)
|
||||
microscope.save_settings()
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Recalibrate can only be used with a Raspberry Pi camera"
|
||||
)
|
||||
|
||||
|
||||
class RecalibrateView(ActionView):
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
import logging
|
||||
import time
|
||||
from fractions import Fraction
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from picamerax import PiCamera
|
||||
from picamerax.array import PiBayerArray, PiRGBArray
|
||||
|
||||
|
||||
def rgb_image(camera, resize=None, **kwargs):
|
||||
def rgb_image(camera: PiCamera, resize: Optional[Tuple[int, int]] = None, **kwargs):
|
||||
"""Capture an image and return an RGB numpy array"""
|
||||
with PiRGBArray(camera, size=resize) as output:
|
||||
camera.capture(output, format="rgb", resize=resize, **kwargs)
|
||||
return output.array
|
||||
|
||||
|
||||
def flat_lens_shading_table(camera):
|
||||
def flat_lens_shading_table(camera: PiCamera):
|
||||
"""Return a flat (i.e. unity gain) lens shading table.
|
||||
|
||||
This is mostly useful because it makes it easy to get the size
|
||||
|
|
@ -24,14 +26,11 @@ def flat_lens_shading_table(camera):
|
|||
raise ImportError(
|
||||
"This program requires the forked picamera library with lens shading support"
|
||||
)
|
||||
return (
|
||||
# pylint: disable=W0212
|
||||
np.zeros(camera._lens_shading_table_shape(), dtype=np.uint8)
|
||||
+ 32
|
||||
)
|
||||
# pylint: disable=protected-access
|
||||
return np.zeros(camera._lens_shading_table_shape(), dtype=np.uint8) + 32
|
||||
|
||||
|
||||
def adjust_exposure_to_setpoint(camera, setpoint):
|
||||
def adjust_exposure_to_setpoint(camera: PiCamera, setpoint: int):
|
||||
"""Adjust the camera's exposure time until the maximum pixel value is <setpoint>."""
|
||||
print("Adjusting shutter speed to hit setpoint {}".format(setpoint), end="")
|
||||
for _ in range(3):
|
||||
|
|
@ -43,7 +42,7 @@ def adjust_exposure_to_setpoint(camera, setpoint):
|
|||
print("done")
|
||||
|
||||
|
||||
def auto_expose_and_freeze_settings(camera):
|
||||
def auto_expose_and_freeze_settings(camera: PiCamera):
|
||||
"""Freeze the settings after auto-exposing to white illumination"""
|
||||
logging.info("Allowing the camera to auto-expose")
|
||||
if "greyworld" in camera.AWB_MODES:
|
||||
|
|
@ -66,7 +65,7 @@ def auto_expose_and_freeze_settings(camera):
|
|||
logging.info("Shutter speed = %s", (camera.shutter_speed))
|
||||
camera.exposure_mode = "off"
|
||||
logging.info("Auto exposure disabled")
|
||||
g = camera.awb_gains
|
||||
g: Tuple[Fraction, Fraction] = camera.awb_gains
|
||||
camera.awb_mode = "off"
|
||||
camera.awb_gains = g
|
||||
logging.info("Auto white balance disabled, gains are %s", (g))
|
||||
|
|
@ -76,10 +75,10 @@ def auto_expose_and_freeze_settings(camera):
|
|||
adjust_exposure_to_setpoint(camera, 215)
|
||||
|
||||
|
||||
def channels_from_bayer_array(bayer_array):
|
||||
def channels_from_bayer_array(bayer_array: np.ndarray) -> np.ndarray:
|
||||
"""Given the 'array' from a PiBayerArray, return the 4 channels."""
|
||||
bayer_pattern = [(i // 2, i % 2) for i in range(4)]
|
||||
channels = np.zeros(
|
||||
bayer_pattern: List[Tuple[int, int]] = [(0, 0), (0, 1), (1, 0), (1, 1)]
|
||||
channels: np.ndarray = np.zeros(
|
||||
(4, bayer_array.shape[0] // 2, bayer_array.shape[1] // 2),
|
||||
dtype=bayer_array.dtype,
|
||||
)
|
||||
|
|
@ -92,14 +91,19 @@ def channels_from_bayer_array(bayer_array):
|
|||
return channels
|
||||
|
||||
|
||||
def lst_from_channels(channels):
|
||||
def lst_from_channels(channels: np.ndarray) -> np.ndarray:
|
||||
"""Given the 4 Bayer colour channels from a white image, generate a LST."""
|
||||
full_resolution = np.array(channels.shape[1:]) * 2 # channels have been binned
|
||||
# lst_resolution = list(np.ceil(full_resolution / 64.0).astype(int))
|
||||
lst_resolution = [(r // 64) + 1 for r in full_resolution]
|
||||
# NB the size of the LST is 1/64th of the image, but rounded UP.
|
||||
full_resolution: np.ndarray = np.array(
|
||||
channels.shape[1:]
|
||||
) * 2 # channels have been binned
|
||||
|
||||
# NOTE: the size of the LST is 1/64th of the image, but rounded UP.
|
||||
lst_resolution: List[int] = [(r // 64) + 1 for r in full_resolution]
|
||||
|
||||
logging.info("Generating a lens shading table at %sx%s", *lst_resolution)
|
||||
lens_shading = np.zeros([channels.shape[0]] + lst_resolution, dtype=np.float)
|
||||
lens_shading: np.ndarray = np.zeros(
|
||||
[channels.shape[0]] + lst_resolution, dtype=np.float
|
||||
)
|
||||
for i in range(lens_shading.shape[0]):
|
||||
image_channel = channels[i, :, :]
|
||||
iw, ih = image_channel.shape
|
||||
|
|
@ -153,7 +157,7 @@ def lst_from_channels(channels):
|
|||
return lens_shading_table[::-1, :, :].copy()
|
||||
|
||||
|
||||
def recalibrate_camera(camera):
|
||||
def recalibrate_camera(camera: PiCamera):
|
||||
"""Reset the lens shading table and exposure settings.
|
||||
|
||||
This method first resets to a flat lens shading table, then auto-exposes,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue