Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions synapse_core/src/synapse/core/camera_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import cv2
import numpy as np
from cscore import (CameraServer, CvSink, UsbCamera, VideoCamera, VideoMode,
VideoSource)
VideoProperty, VideoSource)
from ntcore import NetworkTable, NetworkTableEntry, NetworkTableInstance
from synapse_net.nt_client import NtClient
from synapse_net.proto.v1 import CalibrationDataProto
Expand Down Expand Up @@ -168,8 +168,7 @@ def generateNoSignalFrame(self, size: Resolution = (640, 480)) -> Frame:
colors = [
(255, 255, 255), # white
(0, 255, 255), # yellow
(255, 255, 0), # cyan
(0, 255, 0), # green
(255, 255, 0), # cyan (0, 255, 0), # green
(255, 0, 255), # magenta
(0, 0, 255), # red
(255, 0, 0), # blue
Expand Down Expand Up @@ -258,6 +257,9 @@ def setProperty(self, prop: str, value: Union[int, float]) -> None: ...
@abstractmethod
def getProperty(self, prop: str) -> Union[int, float, None]: ...

def getProperties(self) -> List[VideoProperty]:
return []

@abstractmethod
def setVideoMode(self, fps: int, width: int, height: int) -> None: ...

Expand Down Expand Up @@ -376,8 +378,8 @@ def __init__(self, name: str) -> None:
self.camera: VideoCamera
self.sink: CvSink
self.propertyMeta: PropertyMetaDict = {}
self._properties: Dict[str, Any] = {}
self._videoModes: List[Any] = []
self._properties: Dict[str, VideoProperty] = {}
self._videoModes: List[VideoMode] = []
self._validVideoModes: List[VideoMode] = []

# --- FIX: Memory Recycling Implementation ---
Expand All @@ -396,6 +398,9 @@ def __init__(self, name: str) -> None:
self._thread: Optional[threading.Thread] = None
self._lock = threading.Lock()

def getProperties(self) -> List:
return list(self._properties.values())

@classmethod
def create(
cls,
Expand All @@ -412,7 +417,6 @@ def create(
inst.camera = UsbCamera(f"USB Camera {index}", path)

inst.sink = CameraServer.getVideo(inst.camera)
inst.sink.getProperty("auto_exposure").set(0)

# Cache properties and metadata
props = inst.camera.enumerateProperties()
Expand All @@ -428,6 +432,7 @@ def create(

# Cache video modes and valid resolutions
inst._videoModes = inst.camera.enumerateVideoModes()
inst.camera.setExposureManual(1)
inst._validVideoModes = [mode for mode in inst._videoModes]

# This will call setVideoMode, which now initializes the buffer pool.
Expand Down Expand Up @@ -536,6 +541,8 @@ def close(self) -> None:
)

def setProperty(self, prop: str, value: Union[int, float, str]) -> None:
if prop == "orientation":
return
if prop == "resolution" and isinstance(value, str):
resolution = value.split("x")
width = int(resolution[0])
Expand Down
6 changes: 1 addition & 5 deletions synapse_core/src/synapse/core/camera_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ def scanCameras(self) -> None:
found.append(info.productId)
newIndex = 0

print(self.requestedCameraUIDs)
if len(self.requestedCameraUIDs.keys()) > 0:
if id in self.requestedCameraUIDs.keys():
newIndex = self.requestedCameraUIDs.pop(id)
Expand Down Expand Up @@ -365,7 +364,7 @@ def addCameraData(

self.streamOutputs[cameraIndex] = self.createStreamOutput(cameraIndex)

ret, frame = camera.grabFrame()
frame = camera.generateNoSignalFrame()

if frame is not None:
self.streamOutputs[cameraIndex].putFrame(
Expand All @@ -381,9 +380,6 @@ def addCameraData(
.replace("mjpg:", "")
)

print(cameraConfig.name)
print(stream)

camera.stream = stream

self.setRecordingStatus(cameraIndex, False)
Expand Down
8 changes: 4 additions & 4 deletions synapse_core/src/synapse/core/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def setSetting(self, setting: Union[Setting, str], value: SettingsValue) -> None
self.settings.setSetting(settingObj, value)
self.onSettingChanged(settingObj, self.getSetting(setting))
elif setting in CameraSettings():
collection = self.getCurrentCameraSettingCollection()
collection = self.getCameraSettings()
assert collection is not None
collection.setSetting(setting, value)
else:
Expand Down Expand Up @@ -309,11 +309,11 @@ def getCameraSetting(self, setting: Union[str, Setting]) -> Optional[Any]:
def setCameraSetting(
self, setting: Union[str, Setting], value: SettingsValue
) -> None:
collection = self.getCurrentCameraSettingCollection()
collection = self.getCameraSettings()
assert collection is not None
collection.setSetting(setting, value)

def getCurrentCameraSettingCollection(self) -> Optional[CameraSettings]:
def getCameraSettings(self) -> CameraSettings:
return self.cameraSettings

def onSettingChanged(self, setting: Setting, value: SettingsValue) -> None:
Expand All @@ -332,7 +332,7 @@ def pipelineToProto(inst: Pipeline, index: int, cameraId: CameraID) -> PipelineP
for key in api.getSettingsSchema().keys()
}

cameraSettings = inst.getCurrentCameraSettingCollection()
cameraSettings = inst.getCameraSettings()
if cameraSettings:
cameraAPI = cameraSettings.getAPI()
settingsValues.update(
Expand Down
50 changes: 24 additions & 26 deletions synapse_core/src/synapse/core/runtime_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def __setupPipelineForCamera(

currPipeline.bind(cameraIndex, camera)

cameraSettings = currPipeline.getCurrentCameraSettingCollection()
cameraSettings = currPipeline.getCameraSettings()

assert cameraSettings is not None

Expand Down Expand Up @@ -348,24 +348,26 @@ def updateSetting(self, prop: str, cameraIndex: CameraID, value: Any) -> None:
)
assert pipeline is not None

settings = self.pipelineHandler.getPipelineSettings(
self.pipelineBindings[cameraIndex], cameraIndex
)
setting = settings.getAPI().getSetting(prop)
camera = self.cameraHandler.getCamera(cameraIndex)
camSettings = pipeline.getCameraSettings().getAPI().settings.keys()

if prop in CameraSettings().getAPI().settings.keys():
if prop in camSettings:
assert camera is not None
camera.setProperty(prop=prop, value=value)
pipeline.setCameraSetting(prop, value)
elif setting is not None:
settings.setSetting(prop, value)
pipeline.onSettingChanged(setting, settings.getSetting(prop))
else:
log.warn(
f"Attempted to set setting {prop} on pipeline #{pipeline.pipelineIndex} but it was not found!"
settings = self.pipelineHandler.getPipelineSettings(
self.pipelineBindings[cameraIndex], cameraIndex
)
return
setting = settings.getAPI().getSetting(prop)
if setting is not None:
pipeline.setSetting(prop, value)
pipeline.onSettingChanged(setting, settings.getSetting(prop))
else:
log.warn(
f"Attempted to set setting {prop} on pipeline #{pipeline.pipelineIndex} but it was not found!"
)
return

self.onSettingChanged.call(prop, value, cameraIndex)

Expand Down Expand Up @@ -726,20 +728,18 @@ def rotateCameraBySettings(self, settings: CameraSettings, frame: Frame) -> Fram

def fixBlackLevelOffset(self, settings: PipelineSettings, frame: Frame) -> Frame:
blackLevelOffset = settings.getSetting("black_level_offset")
if blackLevelOffset is None or blackLevelOffset == 0:
return frame

if blackLevelOffset == 0 or blackLevelOffset is None:
return frame # No adjustment needed

blackLevelOffset = -blackLevelOffset / 100
# Normalize to [0,1] and convert to float32
image = frame.astype(np.float32) / 255.0

# Convert to float32 for better precision
image = frame.astype(np.float32) / 255.0 # Normalize to range [0,1]
# Apply black level offset (scaled)
offset = blackLevelOffset / 100.0
image = np.clip(image + offset, 0, 1)

# Apply black level offset: lift only the darkest values
image = np.power(image + blackLevelOffset, 1.0) # Apply a soft offset

# Clip to valid range and convert back to uint8
return np.clip(image * 255, 0, 255).astype(np.uint8)
# Convert back to uint8
return (image * 255).astype(np.uint8)

def fixtureFrame(self, cameraIndex: CameraID, frame: Frame) -> Frame:
if (
Expand All @@ -752,9 +752,7 @@ def fixtureFrame(self, cameraIndex: CameraID, frame: Frame) -> Frame:
)
if pipeline is None:
return frame
settings: Optional[CameraSettings] = (
pipeline.getCurrentCameraSettingCollection()
)
settings: Optional[CameraSettings] = pipeline.getCameraSettings()
if settings is not None:
frame = self.rotateCameraBySettings(settings, frame)

Expand Down
Loading
Loading