Update tuning file utils for our own tuning files.
This commit is contained in:
parent
f74a51866a
commit
ec16cc17f8
3 changed files with 70 additions and 49 deletions
|
|
@ -112,7 +112,7 @@ class SensorModeSelector(BaseModel):
|
||||||
|
|
||||||
These values are the output size and the bit depth.
|
These values are the output size and the bit depth.
|
||||||
|
|
||||||
This is a Pydantic model so that it can be saved to disk.
|
This is a Pydantic model so that it can sent by FastAPI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
output_size: tuple[int, int]
|
output_size: tuple[int, int]
|
||||||
|
|
@ -126,12 +126,13 @@ class LensShading(BaseModel):
|
||||||
(12, 16) in size. The arrays are luminance, red-difference chroma (Cr), and
|
(12, 16) in size. The arrays are luminance, red-difference chroma (Cr), and
|
||||||
blue-difference chroma (Cb).
|
blue-difference chroma (Cb).
|
||||||
|
|
||||||
This is a Pydantic model so that it can be saved to the disk.
|
This is a Pydantic model so that it can sent by FastAPI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
luminance: list[list[float]]
|
luminance: list[list[float]]
|
||||||
Cr: list[list[float]]
|
Cr: list[list[float]]
|
||||||
Cb: list[list[float]]
|
Cb: list[list[float]]
|
||||||
|
colour_temp: int
|
||||||
|
|
||||||
|
|
||||||
class StreamingPiCamera2(BaseCamera):
|
class StreamingPiCamera2(BaseCamera):
|
||||||
|
|
@ -216,7 +217,8 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
@lt.thing_property
|
@lt.thing_property
|
||||||
def calibration_required(self) -> bool:
|
def calibration_required(self) -> bool:
|
||||||
"""Whether the camera needs calibrating."""
|
"""Whether the camera needs calibrating."""
|
||||||
return not self.lens_shading_is_static
|
# Check if the lens shading table is calibrated.
|
||||||
|
return not tf_utils.lst_calibrated(self.tuning)
|
||||||
|
|
||||||
## Persistent controls! These are settings
|
## Persistent controls! These are settings
|
||||||
|
|
||||||
|
|
@ -730,7 +732,12 @@ 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
|
||||||
self.tuning = tf_utils.set_static_lst(self.tuning, L, Cr, Cb)
|
self.tuning = tf_utils.set_lst(
|
||||||
|
self.tuning,
|
||||||
|
luminance=L,
|
||||||
|
cr=Cr,
|
||||||
|
cb=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()
|
||||||
|
|
@ -750,14 +757,14 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
It is a 9 value tuple used to specify the 3x3 matrix that the GPU pipeline uses
|
It is a 9 value tuple used to specify the 3x3 matrix that the GPU pipeline uses
|
||||||
to convert from the camera R,G,B vector to the standard R,G,B.
|
to convert from the camera R,G,B vector to the standard R,G,B.
|
||||||
"""
|
"""
|
||||||
return tuple(tf_utils.get_static_ccm(self.tuning)[0]["ccm"])
|
return tuple(tf_utils.get_ccm(self.tuning)[0]["ccm"])
|
||||||
|
|
||||||
@colour_correction_matrix.setter # type: ignore
|
@colour_correction_matrix.setter # type: ignore
|
||||||
def colour_correction_matrix(
|
def colour_correction_matrix(
|
||||||
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:
|
||||||
self.tuning = tf_utils.set_static_ccm(self.tuning, value)
|
self.tuning = tf_utils.set_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):
|
||||||
|
|
@ -833,8 +840,11 @@ 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))
|
||||||
self.tuning = tf_utils.set_static_lst(
|
self.tuning = tf_utils.set_lst(
|
||||||
self.tuning, flat_array, flat_array, flat_array
|
self.tuning,
|
||||||
|
luminance=flat_array,
|
||||||
|
cr=flat_array,
|
||||||
|
cb=flat_array,
|
||||||
)
|
)
|
||||||
self._initialise_picamera()
|
self._initialise_picamera()
|
||||||
|
|
||||||
|
|
@ -930,9 +940,6 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
- adaptive control is enabled
|
- adaptive control is enabled
|
||||||
- multiple LSTs in use (for different colour temperatures),
|
- multiple LSTs in use (for different colour temperatures),
|
||||||
"""
|
"""
|
||||||
if not self.lens_shading_is_static:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Note "alsc" is the Picamera2 term for "Automatic Lens Shading Correction"
|
# Note "alsc" is the Picamera2 term for "Automatic Lens Shading Correction"
|
||||||
alsc = self.get_tuning_algo("rpi.alsc")
|
alsc = self.get_tuning_algo("rpi.alsc")
|
||||||
|
|
||||||
|
|
@ -951,23 +958,25 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
luminance=reshape_lst(alsc["luminance_lut"]),
|
luminance=reshape_lst(alsc["luminance_lut"]),
|
||||||
Cr=reshape_lst(alsc["calibrations_Cr"][0]["table"]),
|
Cr=reshape_lst(alsc["calibrations_Cr"][0]["table"]),
|
||||||
Cb=reshape_lst(alsc["calibrations_Cb"][0]["table"]),
|
Cb=reshape_lst(alsc["calibrations_Cb"][0]["table"]),
|
||||||
|
colour_temp=alsc["calibrations_Cb"][0]["ct"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@lens_shading_tables.setter
|
@lens_shading_tables.setter
|
||||||
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):
|
||||||
self.tuning = tf_utils.set_static_lst(
|
self.tuning = tf_utils.set_lst(
|
||||||
self.tuning,
|
self.tuning,
|
||||||
luminance=lst.luminance,
|
luminance=lst.luminance,
|
||||||
cr=lst.Cr,
|
cr=lst.Cr,
|
||||||
cb=lst.Cb,
|
cb=lst.Cb,
|
||||||
|
colour_temp=lst.colour_temp,
|
||||||
)
|
)
|
||||||
self._initialise_picamera()
|
self._initialise_picamera()
|
||||||
|
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def flat_lens_shading_chrominance(self) -> None:
|
def flat_lens_shading_chrominance(self) -> None:
|
||||||
"""Disable flat-field correction.
|
"""Disable flat-field correction for colour only.
|
||||||
|
|
||||||
This method will set the chrominance of the lens shading table to be
|
This method will set the chrominance of the lens shading table to be
|
||||||
flat, i.e. we'll correct vignetting of intensity, but not any change in
|
flat, i.e. we'll correct vignetting of intensity, but not any change in
|
||||||
|
|
@ -977,7 +986,13 @@ 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))
|
||||||
self.tuning = tf_utils.set_static_lst(self.tuning, luminance, flat, flat)
|
self.tuning = tf_utils.set_lst(
|
||||||
|
self.tuning,
|
||||||
|
luminance=luminance,
|
||||||
|
cr=flat,
|
||||||
|
cb=flat,
|
||||||
|
colour_temp=1234,
|
||||||
|
)
|
||||||
self._initialise_picamera()
|
self._initialise_picamera()
|
||||||
|
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
|
|
@ -995,17 +1010,6 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
)
|
)
|
||||||
self._initialise_picamera()
|
self._initialise_picamera()
|
||||||
|
|
||||||
@lt.thing_property
|
|
||||||
def lens_shading_is_static(self) -> bool:
|
|
||||||
"""Whether the lens shading is static.
|
|
||||||
|
|
||||||
This property is true if the lens shading correction has been set to use
|
|
||||||
a static table (i.e. the number of automatic correction iterations is zero).
|
|
||||||
The default LST is not static, but all the calibration controls will set it
|
|
||||||
to be static (except "reset")
|
|
||||||
"""
|
|
||||||
return tf_utils.lst_is_static(self.tuning)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def thing_state(self) -> Mapping[str, Any]:
|
def thing_state(self) -> Mapping[str, Any]:
|
||||||
"""Update generic camera metadata with Picamera-specific data."""
|
"""Update generic camera metadata with Picamera-specific data."""
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
"""Functions for loading, adjusting, or reading from the Picamera2 tuning file.
|
"""Functions for loading, adjusting, or reading from the Picamera2 tuning file.
|
||||||
|
|
||||||
The functions that edit the tuning files edit them in place. This will change in
|
The functions that edit the tuning files return a new dictionary that is updated.
|
||||||
the future.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
@ -60,44 +59,65 @@ def find_tuning_algo(tuning: dict[str, dict], name: str) -> dict[str, Any]:
|
||||||
return algo_dict[name]
|
return algo_dict[name]
|
||||||
|
|
||||||
|
|
||||||
def set_static_lst(
|
def set_lst(
|
||||||
tuning: dict,
|
tuning: dict,
|
||||||
|
*,
|
||||||
luminance: np.ndarray,
|
luminance: np.ndarray,
|
||||||
cr: np.ndarray,
|
cr: np.ndarray,
|
||||||
cb: np.ndarray,
|
cb: np.ndarray,
|
||||||
|
colour_temp: int = 5000,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Update the ``rpi.alsc`` section of a camera tuning dict to use a static correction.
|
"""Update the ``rpi.alsc`` section of with new lens shading tables.
|
||||||
|
|
||||||
``tuning`` will be updated in-place to set its shading to static, and disable any
|
Only one set of tables is set so no adaptive lens shading will be used.
|
||||||
adaptive tweaking by the algorithm.
|
|
||||||
|
:param tuning: The current tuning file.
|
||||||
|
:param luminance: The table of luminance values, as (12, 16) numpy array
|
||||||
|
:param cr: The table of cr values, as (12, 16) numpy array
|
||||||
|
:param cb: The table of cb values, as (12, 16) numpy array
|
||||||
|
:param colour_temp: The colour temperature to set. By default this is 5000. Set a
|
||||||
|
different value for the PiCamera Thing to report that the lens shading is not
|
||||||
|
calibrated.
|
||||||
|
:return: an updated tuning dict with the new lens shading tables.
|
||||||
"""
|
"""
|
||||||
output_tuning = deepcopy(tuning)
|
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 = find_tuning_algo(output_tuning, "rpi.alsc")
|
alsc = 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"] = [
|
||||||
{"ct": 4500, "table": _as_flat_rounded_list(cr, round_to=3)}
|
{"ct": colour_temp, "table": _as_flat_rounded_list(cr, round_to=3)}
|
||||||
]
|
]
|
||||||
alsc["calibrations_Cb"] = [
|
alsc["calibrations_Cb"] = [
|
||||||
{"ct": 4500, "table": _as_flat_rounded_list(cb, round_to=3)}
|
{"ct": colour_temp, "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
|
return output_tuning
|
||||||
|
|
||||||
|
|
||||||
def set_static_ccm(
|
def lst_calibrated(tuning: dict) -> bool:
|
||||||
|
"""Whether the lens shading table is calibrated.
|
||||||
|
|
||||||
|
This checks whether the lens shading table is has a colour temperature of 5000. As
|
||||||
|
this is what we set on calibration. Our tuning file sets a temperature of 1234.
|
||||||
|
"""
|
||||||
|
alsc = find_tuning_algo(tuning, "rpi.alsc")
|
||||||
|
return alsc["calibrations_Cr"][0]["ct"] == 5000
|
||||||
|
|
||||||
|
|
||||||
|
def set_ccm(
|
||||||
tuning: dict,
|
tuning: dict,
|
||||||
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
|
||||||
],
|
],
|
||||||
) -> dict:
|
) -> 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 set the colour correction matrix.
|
||||||
|
|
||||||
``tuning`` will be updated in-place to set its shading to static, and disable any
|
:param tuning: The current tuning dict
|
||||||
adaptive tweaking by the algorithm.
|
:param col_corr_matrix: The colour correction matrix to set
|
||||||
|
:return: an updated tuning dict with the new colour correction matrix.
|
||||||
"""
|
"""
|
||||||
output_tuning = deepcopy(tuning)
|
output_tuning = deepcopy(tuning)
|
||||||
ccm = find_tuning_algo(output_tuning, "rpi.ccm")
|
ccm = find_tuning_algo(output_tuning, "rpi.ccm")
|
||||||
|
|
@ -105,32 +125,26 @@ def set_static_ccm(
|
||||||
return output_tuning
|
return output_tuning
|
||||||
|
|
||||||
|
|
||||||
def get_static_ccm(tuning: dict) -> None:
|
def get_ccm(tuning: dict) -> None:
|
||||||
"""Get a copy of the the ``rpi.ccm`` section of a camera tuning dict."""
|
"""Get a copy of the the ``rpi.ccm`` section of a camera tuning dict."""
|
||||||
ccm = find_tuning_algo(tuning, "rpi.ccm")
|
ccm = find_tuning_algo(tuning, "rpi.ccm")
|
||||||
return deepcopy(ccm["ccms"])
|
return deepcopy(ccm["ccms"])
|
||||||
|
|
||||||
|
|
||||||
def lst_is_static(tuning: dict) -> bool:
|
|
||||||
"""Whether the lens shading table is set to static."""
|
|
||||||
alsc = find_tuning_algo(tuning, "rpi.alsc")
|
|
||||||
return alsc["n_iter"] == 0
|
|
||||||
|
|
||||||
|
|
||||||
def set_static_geq(
|
def set_static_geq(
|
||||||
tuning: dict,
|
tuning: dict,
|
||||||
offset: int = 65535,
|
offset: int = 65535,
|
||||||
) -> dict:
|
) -> 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 dictionary
|
||||||
set the geq offset to the given value.
|
|
||||||
:param offset: The desired green equalisation offset. Default 65535. The default is
|
:param offset: The desired green equalisation offset. Default 65535. The default is
|
||||||
the maximum allowed value. This means the brightness will always be below the
|
the maximum allowed value. This means the brightness will always be below the
|
||||||
threshold where averaging is used. This is default as we always need the green
|
threshold where averaging is used. This is default as we always need the green
|
||||||
equalisation to averages the green pixels in the red and blue rows due to the
|
equalisation to averages the green pixels in the red and blue rows due to the
|
||||||
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.
|
||||||
|
:param return: An updated tuning dictionary
|
||||||
"""
|
"""
|
||||||
output_tuning = deepcopy(tuning)
|
output_tuning = deepcopy(tuning)
|
||||||
geq = find_tuning_algo(output_tuning, "rpi.geq")
|
geq = find_tuning_algo(output_tuning, "rpi.geq")
|
||||||
|
|
@ -182,7 +196,7 @@ def copy_algo_from_other_tuning(
|
||||||
# Find the relevant sub-dict for each tuning file
|
# Find the relevant sub-dict for each tuning file
|
||||||
from_i = _index_of_algorithm(copy_from["algorithms"], algo)
|
from_i = _index_of_algorithm(copy_from["algorithms"], algo)
|
||||||
to_i = _index_of_algorithm(base_tuning_file["algorithms"], algo)
|
to_i = _index_of_algorithm(base_tuning_file["algorithms"], algo)
|
||||||
# Updating the dictionary in place.
|
# Updating the output_tuning copy.
|
||||||
output_tuning["algorithms"][to_i] = deepcopy(copy_from["algorithms"][from_i])
|
output_tuning["algorithms"][to_i] = deepcopy(copy_from["algorithms"][from_i])
|
||||||
return output_tuning
|
return output_tuning
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,6 @@ def test_thing_description_equivalence(mock_picam_thing):
|
||||||
"colour_correction_matrix",
|
"colour_correction_matrix",
|
||||||
"lens_shading_tables",
|
"lens_shading_tables",
|
||||||
"sensor_resolution",
|
"sensor_resolution",
|
||||||
"lens_shading_is_static",
|
|
||||||
"capture_metadata",
|
"capture_metadata",
|
||||||
"camera_configuration",
|
"camera_configuration",
|
||||||
"stream_resolution",
|
"stream_resolution",
|
||||||
|
|
@ -131,5 +130,9 @@ def test_thing_description_equivalence(mock_picam_thing):
|
||||||
"sensor_modes",
|
"sensor_modes",
|
||||||
"sensor_mode",
|
"sensor_mode",
|
||||||
}
|
}
|
||||||
|
for action in picamera_extra_actions:
|
||||||
|
assert action in picamera_actions
|
||||||
|
for props in picamera_extra_props:
|
||||||
|
assert props in picamera_props
|
||||||
assert picamera_actions - base_actions == picamera_extra_actions
|
assert picamera_actions - base_actions == picamera_extra_actions
|
||||||
assert picamera_props - base_props == picamera_extra_props
|
assert picamera_props - base_props == picamera_extra_props
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue