Update the picamera tuning file utilities to not modify the dictionaries in place

This commit is contained in:
Julian Stirling 2025-10-23 17:12:18 +01:00
parent 93ff7999f5
commit 815b112069
2 changed files with 51 additions and 27 deletions

View file

@ -730,7 +730,7 @@ class StreamingPiCamera2(BaseCamera):
# luminance (L), red-difference chroma (Cr), and blue-difference chroma # luminance (L), red-difference chroma (Cr), and blue-difference chroma
# (Cb). # (Cb).
L, Cr, Cb = recalibrate_utils.lst_from_camera(cam, self._sensor_info) # noqa: N806 L, Cr, Cb = recalibrate_utils.lst_from_camera(cam, self._sensor_info) # noqa: N806
tf_utils.set_static_lst(self.tuning, L, Cr, Cb) self.tuning = tf_utils.set_static_lst(self.tuning, L, Cr, Cb)
# Re-initialise the picamera to reload the tuning file. # Re-initialise the picamera to reload the tuning file.
self._initialise_picamera() self._initialise_picamera()
@ -760,7 +760,7 @@ class StreamingPiCamera2(BaseCamera):
self, self,
value: tuple[float, float, float, float, float, float, float, float, float], value: tuple[float, float, float, float, float, float, float, float, float],
) -> None: ) -> None:
tf_utils.set_static_ccm(self.tuning, value) self.tuning = tf_utils.set_static_ccm(self.tuning, value)
if self._picamera is not None: if self._picamera is not None:
with self._streaming_picamera(pause_stream=True): with self._streaming_picamera(pause_stream=True):
@ -813,7 +813,7 @@ class StreamingPiCamera2(BaseCamera):
A value of 0 here does nothing, a value of 65535 is maximum correction. A value of 0 here does nothing, a value of 65535 is maximum correction.
""" """
with self._streaming_picamera(pause_stream=True): with self._streaming_picamera(pause_stream=True):
tf_utils.set_static_geq(self.tuning, offset) self.tuning = tf_utils.set_static_geq(self.tuning, offset)
self._initialise_picamera() self._initialise_picamera()
@lt.thing_action @lt.thing_action
@ -824,7 +824,7 @@ class StreamingPiCamera2(BaseCamera):
of view, causing inconsistent settings when capturing. of view, causing inconsistent settings when capturing.
""" """
with self._streaming_picamera(pause_stream=True): with self._streaming_picamera(pause_stream=True):
tf_utils.set_ce_to_disabled(self.tuning) self.tuning = tf_utils.set_ce_to_disabled(self.tuning)
self._initialise_picamera() self._initialise_picamera()
@lt.thing_action @lt.thing_action
@ -863,7 +863,9 @@ class StreamingPiCamera2(BaseCamera):
with self._streaming_picamera(pause_stream=True): with self._streaming_picamera(pause_stream=True):
# Generate and array of ones of the correct size for each channel # Generate and array of ones of the correct size for each channel
flat_array = np.ones((12, 16)) flat_array = np.ones((12, 16))
tf_utils.set_static_lst(self.tuning, flat_array, flat_array, flat_array) self.tuning = tf_utils.set_static_lst(
self.tuning, flat_array, flat_array, flat_array
)
self._initialise_picamera() self._initialise_picamera()
@lt.thing_property @lt.thing_property
@ -985,7 +987,7 @@ class StreamingPiCamera2(BaseCamera):
def lens_shading_tables(self, lst: LensShading) -> None: def lens_shading_tables(self, lst: LensShading) -> None:
"""Set the lens shading tables.""" """Set the lens shading tables."""
with self._streaming_picamera(pause_stream=True): with self._streaming_picamera(pause_stream=True):
tf_utils.set_static_lst( self.tuning = tf_utils.set_static_lst(
self.tuning, self.tuning,
luminance=lst.luminance, luminance=lst.luminance,
cr=lst.Cr, cr=lst.Cr,
@ -1005,7 +1007,7 @@ class StreamingPiCamera2(BaseCamera):
alsc = self.get_tuning_algo("rpi.alsc") alsc = self.get_tuning_algo("rpi.alsc")
luminance = alsc["luminance_lut"] luminance = alsc["luminance_lut"]
flat = np.ones((12, 16)) flat = np.ones((12, 16))
tf_utils.set_static_lst(self.tuning, luminance, flat, flat) self.tuning = tf_utils.set_static_lst(self.tuning, luminance, flat, flat)
self._initialise_picamera() self._initialise_picamera()
@lt.thing_action @lt.thing_action
@ -1016,7 +1018,9 @@ class StreamingPiCamera2(BaseCamera):
by the Raspberry Pi camera. by the Raspberry Pi camera.
""" """
with self._streaming_picamera(pause_stream=True): with self._streaming_picamera(pause_stream=True):
tf_utils.copy_alsc_section(self.default_tuning, self.tuning) self.tuning = tf_utils.copy_tuning_with_alsc_section_from_other(
base_tuning_file=self.tuning, copy_alsc_from=self.default_tuning
)
self._initialise_picamera() self._initialise_picamera()
@lt.thing_property @lt.thing_property

View file

@ -4,6 +4,8 @@ The functions that edit the tuning files edit them in place. This will change in
the future. the future.
""" """
from copy import deepcopy
from picamera2 import Picamera2 from picamera2 import Picamera2
import numpy as np import numpy as np
@ -31,16 +33,17 @@ def set_static_lst(
luminance: np.ndarray, luminance: np.ndarray,
cr: np.ndarray, cr: np.ndarray,
cb: np.ndarray, cb: np.ndarray,
) -> None: ) -> dict:
"""Update the ``rpi.alsc`` section of a camera tuning dict to use a static correction. """Update the ``rpi.alsc`` section of a camera tuning dict to use a static correction.
``tuning`` will be updated in-place to set its shading to static, and disable any ``tuning`` will be updated in-place to set its shading to static, and disable any
adaptive tweaking by the algorithm. adaptive tweaking by the algorithm.
""" """
output_tuning = deepcopy(tuning)
for table in luminance, cr, cb: for table in luminance, cr, cb:
if np.array(table).shape != (12, 16): if np.array(table).shape != (12, 16):
raise ValueError("Lens shading tables must be 12x16!") raise ValueError("Lens shading tables must be 12x16!")
alsc = Picamera2.find_tuning_algo(tuning, "rpi.alsc") alsc = Picamera2.find_tuning_algo(output_tuning, "rpi.alsc")
alsc["n_iter"] = 0 # disable the adaptive part alsc["n_iter"] = 0 # disable the adaptive part
alsc["luminance_strength"] = 1.0 alsc["luminance_strength"] = 1.0
alsc["calibrations_Cr"] = [ alsc["calibrations_Cr"] = [
@ -50,6 +53,7 @@ def set_static_lst(
{"ct": 4500, "table": _as_flat_rounded_list(cb, round_to=3)} {"ct": 4500, "table": _as_flat_rounded_list(cb, round_to=3)}
] ]
alsc["luminance_lut"] = _as_flat_rounded_list(luminance, round_to=3) alsc["luminance_lut"] = _as_flat_rounded_list(luminance, round_to=3)
return output_tuning
def set_static_ccm( def set_static_ccm(
@ -57,20 +61,22 @@ def set_static_ccm(
col_corr_matrix: tuple[ col_corr_matrix: tuple[
float, float, float, float, float, float, float, float, float float, float, float, float, float, float, float, float, float
], ],
) -> None: ) -> dict:
"""Update the ``rpi.alsc`` section of a camera tuning dict to use a static correction. """Update the ``rpi.alsc`` section of a camera tuning dict to use a static correction.
``tuning`` will be updated in-place to set its shading to static, and disable any ``tuning`` will be updated in-place to set its shading to static, and disable any
adaptive tweaking by the algorithm. adaptive tweaking by the algorithm.
""" """
ccm = Picamera2.find_tuning_algo(tuning, "rpi.ccm") output_tuning = deepcopy(tuning)
ccm = Picamera2.find_tuning_algo(output_tuning, "rpi.ccm")
ccm["ccms"] = [{"ct": 5000, "ccm": col_corr_matrix}] ccm["ccms"] = [{"ct": 5000, "ccm": col_corr_matrix}]
return output_tuning
def get_static_ccm(tuning: dict) -> None: def get_static_ccm(tuning: dict) -> None:
"""Get the ``rpi.ccm`` section of a camera tuning dict.""" """Get a copy of the the ``rpi.ccm`` section of a camera tuning dict."""
ccm = Picamera2.find_tuning_algo(tuning, "rpi.ccm") ccm = Picamera2.find_tuning_algo(tuning, "rpi.ccm")
return ccm["ccms"] return deepcopy(ccm["ccms"])
def lst_is_static(tuning: dict) -> bool: def lst_is_static(tuning: dict) -> bool:
@ -82,7 +88,7 @@ def lst_is_static(tuning: dict) -> bool:
def set_static_geq( def set_static_geq(
tuning: dict, tuning: dict,
offset: int = 65535, offset: int = 65535,
) -> None: ) -> dict:
"""Update the ``rpi.geq`` section of a camera tuning dict. """Update the ``rpi.geq`` section of a camera tuning dict.
:param tuning: the raspberry pi tuning file. This will be updated in-place to :param tuning: the raspberry pi tuning file. This will be updated in-place to
@ -94,8 +100,11 @@ def set_static_geq(
chief ray angle compensation issue when the the stock lens is replaced by an chief ray angle compensation issue when the the stock lens is replaced by an
objective. objective.
""" """
geq = Picamera2.find_tuning_algo(tuning, "rpi.geq") output_tuning = deepcopy(tuning)
geq["offset"] = offset # max out offset to disable the adaptive green equalisation geq = Picamera2.find_tuning_algo(output_tuning, "rpi.geq")
# max out offset to disable the adaptive green equalisation
geq["offset"] = offset
return output_tuning
def geq_is_static(tuning: dict) -> bool: def geq_is_static(tuning: dict) -> bool:
@ -106,14 +115,16 @@ def geq_is_static(tuning: dict) -> bool:
def set_ce_to_disabled( def set_ce_to_disabled(
tuning: dict, tuning: dict,
) -> None: ) -> dict:
"""Set ``ce_enable`` in ``rpi.contrast`` to zero to disable adaptive contrast enhancement. """Set ``ce_enable`` in ``rpi.contrast`` to zero to disable adaptive contrast enhancement.
:param tuning: the raspberry pi tuning file. This will be updated in-place to :param tuning: The raspberry pi tuning file.
set ce_enable to 0. :returns: A deepcopu of the input file set ce_enable to 0.
""" """
contrast = Picamera2.find_tuning_algo(tuning, "rpi.contrast") output_tuning = deepcopy(tuning)
contrast = Picamera2.find_tuning_algo(output_tuning, "rpi.contrast")
contrast["ce_enable"] = 0 contrast["ce_enable"] = 0
return output_tuning
def ce_enable_is_static(tuning: dict) -> bool: def ce_enable_is_static(tuning: dict) -> bool:
@ -122,16 +133,25 @@ def ce_enable_is_static(tuning: dict) -> bool:
return contrast["ce_enable"] == 0 return contrast["ce_enable"] == 0
def copy_alsc_section(from_tuning: dict, to_tuning: dict) -> None: def copy_tuning_with_alsc_section_from_other(
"""Copy the ``rpi.alsc`` algorithm from one tuning to another. *, base_tuning_file: dict, copy_alsc_from: dict
) -> dict:
"""Return a copy of tuning_file with the lens shading correction from another file.
This is done in-place, i.e. modifying to_tuning. All parameters are keyword only for clarity.
:param base_tuning_file: The tuning file to copy.
:param copy_alsc_from: The tuning file to take the alsc section from.
:return: A deep copy of base_tuning_file with the alsc section copied in from the
other tuning file.
""" """
output_tuning = deepcopy(base_tuning_file)
# Using Picamera2 function to find the relevant sub-dict for each tuning file # Using Picamera2 function to find the relevant sub-dict for each tuning file
from_i = _index_of_algorithm(from_tuning["algorithms"], "rpi.alsc") from_i = _index_of_algorithm(copy_alsc_from["algorithms"], "rpi.alsc")
to_i = _index_of_algorithm(to_tuning["algorithms"], "rpi.alsc") to_i = _index_of_algorithm(base_tuning_file["algorithms"], "rpi.alsc")
# Updating the dictionary in place. # Updating the dictionary in place.
to_tuning["algorithms"][to_i] = from_tuning["algorithms"][from_i] output_tuning["algorithms"][to_i] = deepcopy(copy_alsc_from["algorithms"][from_i])
return output_tuning
def _index_of_algorithm(algorithms: list[dict], algorithm: str) -> int: def _index_of_algorithm(algorithms: list[dict], algorithm: str) -> int: