-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Expand camera control ability (#14)
- Loading branch information
Showing
11 changed files
with
370 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
Spyglass offers a few CLI parameters for the most commonly used camera controls. | ||
Controls not directly available through the CLI can be used with the `--controls` (`-c`) or `--controls-string` (`-cs`) parameters or the `CONTROLS` section inside the `spyglass.conf`. | ||
|
||
|
||
## How to list available controls? | ||
|
||
Spyglass provides a CLI parameter to list all available controls `--list-controls`. The available controls are then printed onto your shell under `Available controls:`. | ||
|
||
Following shows an example for a Raspberry Pi Module v3: | ||
```sh | ||
Available controls: | ||
NoiseReductionMode (int) : min=0 max=4 default=0 | ||
ScalerCrop (tuple) : min=(0, 0, 0, 0) max=(65535, 65535, 65535, 65535) default=(0, 0, 0, 0) | ||
Sharpness (float) : min=0.0 max=16.0 default=1.0 | ||
AwbEnable (bool) : min=False max=True default=None | ||
FrameDurationLimits (int) : min=33333 max=120000 default=None | ||
ExposureValue (float) : min=-8.0 max=8.0 default=0.0 | ||
AwbMode (int) : min=0 max=7 default=0 | ||
AeExposureMode (int) : min=0 max=3 default=0 | ||
Brightness (float) : min=-1.0 max=1.0 default=0.0 | ||
AfWindows (tuple) : min=(0, 0, 0, 0) max=(65535, 65535, 65535, 65535) default=(0, 0, 0, 0) | ||
AfSpeed (int) : min=0 max=1 default=0 | ||
AfTrigger (int) : min=0 max=1 default=0 | ||
LensPosition (float) : min=0.0 max=32.0 default=1.0 | ||
AfRange (int) : min=0 max=2 default=0 | ||
AfPause (int) : min=0 max=2 default=0 | ||
ExposureTime (int) : min=0 max=66666 default=None | ||
AeEnable (bool) : min=False max=True default=None | ||
AeConstraintMode (int) : min=0 max=3 default=0 | ||
AfMode (int) : min=0 max=2 default=0 | ||
AnalogueGain (float) : min=1.0 max=16.0 default=None | ||
ColourGains (float) : min=0.0 max=32.0 default=None | ||
AfMetering (int) : min=0 max=1 default=0 | ||
AeMeteringMode (int) : min=0 max=3 default=0 | ||
Contrast (float) : min=0.0 max=32.0 default=1.0 | ||
Saturation (float) : min=0.0 max=32.0 default=1.0 | ||
``` | ||
|
||
|
||
## How to apply a camera control? | ||
|
||
There are multiple ways to apply a camera control. All methods are case insensitive. | ||
|
||
### Shell | ||
|
||
There are two different parameters to apply the controls: | ||
|
||
- `--controls`/`-c` can be used multiple times, to set multiple controls. E.g. using `-c brightness=0.5 -c awbenable=false` will apply `0.5` to the `Brightness` and `False` as the new `AwbEnable` value. | ||
- `--controls-string`/`cs` can be used only once. E.g. using `--controls-string "brightness=0.5, awbenable=16"` will apply `0.5` on the `Brightness` and `False` as the new `AwbEnable` control. Note: The `"` are required and the controls need to be separated by a `,`. This is intended only for parsing the config. | ||
|
||
### Config | ||
|
||
The `spyglass.conf` accepts camera controls under the `CONTROLS` option. E.g. `CONTROLS="brightness=0,awbenable=false"` will apply `0.5` to the `Brightness` and `False` as the new `AwbEnable` value. | ||
|
||
### Webinterface | ||
|
||
Spyglass also provides an API endpoint to change the camera controls during runtime. This endpoint is available under `http://<ip.of.your.pi>:<port>/controls` and cannot be changed. | ||
|
||
Calling it without any parameters will show you a list of all available controls, like `--list-controls`. | ||
|
||
E.g. `http://<ip.of.your.pi>:<port>/controls?brightness=0.5&awbenable=false` will apply `0.5` to the `Brightness` and `False` as the new `AwbEnable` value. | ||
|
||
If you apply parameters the interface will show you the parameters Spyglass found inside the url and which controls got actually processed: | ||
- `Parsed Controls` shows you the parameters Spyglass found during the request. | ||
- `Processed Controls` shows you the parameters of the `Parsed Controls` Spyglass could actually set for the cam. | ||
|
||
E.g. `http://<ip.of.your.pi>:<port>/controls?brightness=0.5&foo=bar&foobar` will show you `Parsed Controls: [('brightness', '1'), ('foo', 'bar')]` and `Processed Controls: {'Brightness': 1}`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
body { | ||
font-family: Arial, sans-serif; | ||
background-color: #f6f6f6; | ||
margin: 40px; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
|
||
.card-container { | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
width: 100%; | ||
max-width: 1100px; | ||
} | ||
|
||
.card-container:nth-child(odd) .card { | ||
background-color: white; | ||
} | ||
|
||
.card-container:nth-child(even) .card { | ||
background-color: #f0f0f0; | ||
} | ||
|
||
.card { | ||
border-radius: 5px; | ||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | ||
padding: 0px 20px 20px 20px; /* top right bottom left */ | ||
width: 80%; | ||
box-sizing: border-box; | ||
transition: 0.3s; | ||
} | ||
|
||
.card:hover { | ||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); | ||
} | ||
|
||
.card h2 { | ||
color: #2C3E50; | ||
border-bottom: 1px solid #2C3E50; | ||
padding-bottom: 10px; | ||
margin-bottom: 10px; | ||
} | ||
|
||
.card-content { | ||
display: flex; | ||
margin-bottom: 1px; | ||
} | ||
|
||
.setting { | ||
flex: 1; | ||
display: flex; | ||
margin: 0 5px; | ||
} | ||
|
||
.label, | ||
.value { | ||
font-size: 14px; | ||
} | ||
|
||
.label { | ||
font-weight: bold; | ||
color: #7F8C8D; | ||
} | ||
|
||
.value { | ||
margin-left: 4px; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import libcamera | ||
import ast | ||
|
||
def parse_dictionary_to_html_page(camera, parsed_controls='None', processed_controls='None'): | ||
html = """ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
""" | ||
html += f""" | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Camera Settings</title> | ||
<style>{get_style()}</style> | ||
</head> | ||
""" | ||
html += f""" | ||
<body> | ||
<h1>Available camera options</h1> | ||
<h3>Parsed Controls: {parsed_controls}</h3> | ||
<h3>Processed Controls: {processed_controls}</h3> | ||
""" | ||
for control, values in camera.camera_controls.items(): | ||
html += f""" | ||
<div class="card-container"> | ||
<div class="card"> | ||
<h2>{control}</h2> | ||
<div class="card-content"> | ||
<div class="setting"> | ||
<span class="label">Min:</span> | ||
<span class="value">{values[0]}</span> | ||
</div> | ||
<div class="setting"> | ||
<span class="label">Max:</span> | ||
<span class="value">{values[1]}</span> | ||
</div> | ||
<div class="setting"> | ||
<span class="label">Default:</span> | ||
<span class="value">{values[2]}</span> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
""" | ||
html += """ | ||
</body> | ||
</html> | ||
""" | ||
return html | ||
|
||
def get_style(): | ||
with (open('resources/controls_style.css', 'r')) as f: | ||
return f.read() | ||
|
||
def process_controls(camera, controls: list[tuple[str, str]]) -> dict[str, any]: | ||
controls_dict_lower = { k.lower(): k for k in camera.camera_controls.keys() } | ||
if controls == None: | ||
return {} | ||
processed_controls = {} | ||
for key, value in controls: | ||
key = key.lower().strip() | ||
if key.lower() in controls_dict_lower.keys(): | ||
value = value.lower().strip() | ||
k = controls_dict_lower[key] | ||
v = parse_from_string(value) | ||
processed_controls[k] = v | ||
return processed_controls | ||
|
||
def parse_from_string(input_string: str) -> any: | ||
try: | ||
return ast.literal_eval(input_string) | ||
except (ValueError, TypeError, SyntaxError): | ||
pass | ||
|
||
if input_string.lower() in ['true', 'false']: | ||
return input_string.lower() == 'true' | ||
|
||
return input_string | ||
|
||
def get_type_str(obj) -> str: | ||
return str(type(obj)).split('\'')[1] | ||
|
||
def get_libcamera_controls_string(camera_path: str) -> str: | ||
ctrls_str = "" | ||
libcam_cm = libcamera.CameraManager.singleton() | ||
cam = libcam_cm.cameras[0] | ||
def rectangle_to_tuple(rectangle): | ||
return (rectangle.x, rectangle.y, rectangle.width, rectangle.height) | ||
for k, v in cam.controls.items(): | ||
if isinstance(v.min, libcamera.Rectangle): | ||
min = rectangle_to_tuple(v.min) | ||
max = rectangle_to_tuple(v.max) | ||
default = rectangle_to_tuple(v.default) | ||
else: | ||
min = v.min | ||
max = v.max | ||
default = v.default | ||
|
||
str_first = f"{k.name} ({get_type_str(min)})" | ||
str_second = f"min={min} max={max} default={default}" | ||
str_indent = (30 - len(str_first)) * ' ' + ': ' | ||
ctrls_str += str_first + str_indent + str_second + '\n' | ||
|
||
return ctrls_str.strip() |
Oops, something went wrong.