Fix camera type issues

This commit is contained in:
Julian Stirling 2025-12-19 11:02:07 +00:00
parent 9976c5cb84
commit 99eee2e912
5 changed files with 22 additions and 42 deletions

View file

@ -288,7 +288,7 @@ class StreamingPiCamera2(BaseCamera):
"Sharpness": 1,
}
_sensor_modes = None
_sensor_modes: Optional[list[SensorMode]] = None
@lt.property
def sensor_modes(self) -> list[SensorMode]:
@ -363,6 +363,9 @@ class StreamingPiCamera2(BaseCamera):
camera_num=self._camera_num,
tuning=self.tuning,
)
if self._picamera is None:
# Type narrow (error if failure)
raise RuntimeError("Failed to start Picamera")
if check_sensor_model:
hw_sensor_model = self._picamera.camera_properties["Model"]
if hw_sensor_model != self._sensor_info.sensor_model:
@ -861,7 +864,7 @@ class StreamingPiCamera2(BaseCamera):
]
@lt.property
def lens_shading_tables(self) -> Optional[tf_utils.LensShading]:
def lens_shading_tables(self) -> Optional[tf_utils.LensShadingModel]:
"""The current lens shading (i.e. flat-field correction).
Return the current lens shading correction, as three 2D lists each with
@ -874,19 +877,6 @@ class StreamingPiCamera2(BaseCamera):
"""
return tf_utils.get_lst(self.tuning)
@lens_shading_tables.setter
def _set_lens_shading_tables(self, lst: tf_utils.LensShading) -> None:
"""Set the lens shading tables."""
with self._streaming_picamera(pause_stream=True):
self.tuning = tf_utils.set_lst(
self.tuning,
luminance=lst.luminance,
cr=lst.Cr,
cb=lst.Cb,
colour_temp=lst.colour_temp,
)
self._initialise_picamera()
@lt.action
def flat_lens_shading(self) -> None:
"""Disable flat-field correction.

View file

@ -51,7 +51,6 @@ import numpy as np
import picamera2
from picamera2 import Picamera2
from pydantic import BaseModel
from scipy.ndimage import zoom
LOGGER = logging.getLogger(__name__)
@ -322,18 +321,7 @@ def _get_16x12_grid(chan: np.ndarray, dx: int, dy: int) -> np.ndarray:
return np.reshape(np.array(grid), (12, 16))
def _upsample_channels(grids: np.ndarray, shape: tuple[int]) -> np.ndarray:
"""Zoom an image in the last two dimensions.
This is effectively the inverse operation of ``_get_16x12_grid``
"""
zoom_factors = [
1,
] + list(np.ceil(np.array(shape) / np.array(grids.shape[1:])))
return zoom(grids, zoom_factors, order=1)[:, : shape[0], : shape[1]]
def _downsampled_channels(channels: np.ndarray, blacklevel: int) -> list[np.ndarray]:
def _downsampled_channels(channels: np.ndarray, blacklevel: int) -> np.ndarray:
"""Generate a downsampled, un-normalised image from which to calculate the LST."""
channel_shape = np.array(channels.shape[1:])
lst_shape = np.array([12, 16])
@ -398,9 +386,7 @@ def _grids_from_lst(lum: np.ndarray, Cr: np.ndarray, Cb: np.ndarray) -> np.ndarr
return np.stack([B, G, G, R], axis=0)
def _raw_channels_from_camera(
camera: Picamera2, sensor_info: SensorInfo
) -> LensShadingTables:
def _raw_channels_from_camera(camera: Picamera2, sensor_info: SensorInfo) -> np.ndarray:
"""Acquire a raw image and return a 4xNxM array of the colour channels."""
if camera.started:
camera.stop_recording()

View file

@ -20,9 +20,12 @@ CALIBRATED_COLOUR_TEMP = 5000
DEFAULT_COLOUR_TEMP = 1234
class LensShading(BaseModel):
class LensShadingModel(BaseModel):
"""A Pydantic model holding the lens shading tables.
Note this shouldn't be confused with the typehint for LensShadingTables in
recalibrate utils which is for the arrays.
PiCamera needs three numpy arrays for lens shading correction. Each array is
(12, 16) in size. The arrays are luminance, red-difference chroma (Cr), and
blue-difference chroma (Cb).
@ -165,7 +168,7 @@ def flatten_lst(tuning: dict, keep_luminance: bool = False) -> dict:
)
def get_lst(tuning: dict) -> LensShading:
def get_lst(tuning: dict) -> LensShadingModel:
"""Return the lens shading as a LenSading Base Model."""
# Note "alsc" is the Picamera2 term for "Automatic Lens Shading Correction"
alsc = find_tuning_algo(tuning, "rpi.alsc")
@ -175,7 +178,7 @@ def get_lst(tuning: dict) -> LensShading:
w, h = 16, 12
return [lin[w * i : w * (i + 1)] for i in range(h)]
return LensShading(
return LensShadingModel(
luminance=reshape_lst(alsc["luminance_lut"]),
Cr=reshape_lst(alsc["calibrations_Cr"][0]["table"]),
Cb=reshape_lst(alsc["calibrations_Cb"][0]["table"]),

View file

@ -33,7 +33,7 @@ from . import BaseCamera
LOGGER = logging.getLogger(__name__)
# The ratio between "motor" steps and pixels
# The ratio between "motor" steps and pixels in (x, y, z)
# higher related to a faster movement
RATIO = (2, 2, 0.2)
@ -203,10 +203,11 @@ class SimulatedCamera(BaseCamera):
"""
canvas_width, canvas_height, _ = self.canvas_shape
image_width, image_height, _ = self.shape
pos = tuple(x * s for x, s in zip(pos, RATIO, strict=True))
# Scale position by RATIO to get position in base image.
im_pos = tuple(x * ratio for x, ratio in zip(pos, RATIO, strict=True))
top_left = (
int(pos[0]) - image_width // 2 + self.sample_limits[0] // 2,
int(pos[1]) - image_height // 2 + self.sample_limits[1] // 2,
int(im_pos[0]) - image_width // 2 + self.sample_limits[0] // 2,
int(im_pos[1]) - image_height // 2 + self.sample_limits[1] // 2,
)
# Create index list with modulo rather than slicing to handle wrapping at the
# canvas edge.
@ -217,7 +218,7 @@ class SimulatedCamera(BaseCamera):
# Use npx to make each 1d index list 3D
focused_image = canvas[np.ix_(x_indices, y_indices, z_indices)]
image = fast_pil_blur(focused_image, sigma=np.abs(pos[2]) / 5)
image = fast_pil_blur(focused_image, sigma=np.abs(im_pos[2]) / 5)
if image.shape != self.shape:
raise ValueError(
@ -251,7 +252,7 @@ class SimulatedCamera(BaseCamera):
_traceback: Optional[TracebackType],
) -> None:
"""Close the capture thread when the Thing context manager is closed."""
if self.stream_active:
if self._capture_thread is not None and self._capture_thread.is_alive():
self._capture_enabled = False
self._capture_thread.join()
@ -300,7 +301,7 @@ class SimulatedCamera(BaseCamera):
try:
frame = self.generate_frame()
self.mjpeg_stream.add_frame(_frame2bytes(frame))
ds_frame = frame.resize((320, 240), resample=Image.NEAREST)
ds_frame = frame.resize((320, 240), resample=Image.Resampling.NEAREST)
self.lores_mjpeg_stream.add_frame(_frame2bytes(ds_frame))
except Exception as e: