Compare commits

..

1 Commits

Author SHA1 Message Date
Matt M
bca7d03294 Backport maven changes 2024-08-02 08:17:46 -07:00
41 changed files with 184 additions and 1071 deletions

View File

@@ -10,29 +10,6 @@ on:
branches: [ master ]
jobs:
build-offline-docs:
name: "Build Offline Docs"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
repository: 'PhotonVision/photonvision-docs.git'
ref: master
- uses: actions/setup-python@v5
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install sphinx sphinx_rtd_theme sphinx-tabs sphinxext-opengraph doc8
pip install -r requirements.txt
- name: Build the docs
run: |
make html
- uses: actions/upload-artifact@master
with:
name: built-docs
path: build/html
build-photonlib-host:
env:
MACOSX_DEPLOYMENT_TARGET: 12

View File

@@ -4,7 +4,7 @@ plugins {
id "com.diffplug.spotless" version "6.24.0"
id "edu.wpi.first.NativeUtils" version "2024.6.1" apply false
id "edu.wpi.first.wpilib.repositories.WPILibRepositoriesPlugin" version "2020.2"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
id 'edu.wpi.first.WpilibTools' version '1.3.0'
id 'com.google.protobuf' version '0.9.4' apply false
}
@@ -25,15 +25,15 @@ allprojects {
apply from: "versioningHelper.gradle"
ext {
wpilibVersion = "2024.3.1"
wpilibVersion = "2024.2.1"
wpimathVersion = wpilibVersion
openCVversion = "4.8.0-2"
joglVersion = "2.4.0"
joglVersion = "2.4.0-rc-20200307"
javalinVersion = "5.6.2"
photonGlDriverLibVersion = "dev-v2023.1.0-11-g2b7036f"
rknnVersion = "dev-v2024.0.1-4-g0db16ac"
photonGlDriverLibVersion = "dev-v2023.1.0-9-g75fc678"
rknnVersion = "dev-v2024.0.0-64-gc0836a6"
frcYear = "2024"
mrcalVersion = "dev-v2024.0.0-24-gc1efcf0";
mrcalVersion = "dev-v2024.0.0-7-gc976aaa";
pubVersion = versionString

View File

@@ -162,9 +162,9 @@ def __convert_cal_to_mrcal_cameramodel(
"indices_point_camintrinsics_camextrinsics": None,
"lensmodel": model,
"imagersizes": np.array([imagersize], dtype=np.int32),
"calobject_warp": (
np.array(cal.calobjectWarp) if len(cal.calobjectWarp) > 0 else None
),
"calobject_warp": np.array(cal.calobjectWarp)
if len(cal.calobjectWarp) > 0
else None,
# We always do all the things
"do_optimize_intrinsics_core": True,
"do_optimize_intrinsics_distortions": True,

View File

@@ -26,9 +26,7 @@ const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
const calib = useCameraSettingsStore().getCalibrationCoeffs(format.resolution);
if (calib !== undefined) {
// For each error, square it, sum the squares, and divide by total points N
if (calib.meanErrors.length)
format.mean = calib.meanErrors.reduce((a, b) => a + b, 0) / calib.meanErrors.length;
else format.mean = NaN;
format.mean = calib.meanErrors.reduce((a, b) => a + b) / calib.meanErrors.length;
format.horizontalFOV =
2 * Math.atan2(format.resolution.width / 2, calib.cameraIntrinsics.data[0]) * (180 / Math.PI);
@@ -258,7 +256,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
>
<td>{{ getResolutionString(value.resolution) }}</td>
<td>
{{ value.mean !== undefined ? (isNaN(value.mean) ? "Unknown" : value.mean.toFixed(2) + "px") : "-" }}
{{ value.mean !== undefined ? (isNaN(value.mean) ? "NaN" : value.mean.toFixed(2) + "px") : "-" }}
</td>
<td>{{ value.horizontalFOV !== undefined ? value.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
<td>{{ value.verticalFOV !== undefined ? value.verticalFOV.toFixed(2) + "°" : "-" }}</td>

View File

@@ -59,8 +59,7 @@ const settingsHaveChanged = (): boolean => {
a.shouldPublishProto !== b.shouldPublishProto ||
a.networkManagerIface !== b.networkManagerIface ||
a.setStaticCommand !== b.setStaticCommand ||
a.setDHCPcommand !== b.setDHCPcommand ||
a.matchCamerasOnlyByPath !== b.matchCamerasOnlyByPath
a.setDHCPcommand !== b.setDHCPcommand
);
};
@@ -78,7 +77,6 @@ const saveGeneralSettings = () => {
setStaticCommand: tempSettingsStruct.value.setStaticCommand || "",
shouldManage: tempSettingsStruct.value.shouldManage,
shouldPublishProto: tempSettingsStruct.value.shouldPublishProto,
matchCamerasOnlyByPath: tempSettingsStruct.value.matchCamerasOnlyByPath,
staticIp: tempSettingsStruct.value.staticIp
};
@@ -139,8 +137,6 @@ watchEffect(() => {
<template>
<v-card dark class="mb-3 pr-6 pb-3" style="background-color: #006492">
<v-card-title>Global Settings</v-card-title>
<v-divider />
<v-card-title>Networking</v-card-title>
<div class="ml-5">
<v-form ref="form" v-model="settingsValid">
@@ -258,9 +254,6 @@ watchEffect(() => {
>
This mode is intended for debugging; it should be off for proper usage. PhotonLib will NOT work!
</v-banner>
<v-divider />
<v-card-title>Miscellaneous</v-card-title>
<pv-switch
v-model="tempSettingsStruct.shouldPublishProto"
label="Also Publish Protobuf"
@@ -279,32 +272,6 @@ watchEffect(() => {
This mode is intended for debugging; it should be off for field use. You may notice a performance hit by using
this mode.
</v-banner>
<pv-switch
v-model="tempSettingsStruct.matchCamerasOnlyByPath"
label="Strictly match ONLY known cameras"
tooltip="ONLY match cameras by the USB port they're plugged into + (basename or USB VID/PID), and never only by the device product string. Also disables automatic detection of new cameras."
class="mt-3 mb-2"
:label-cols="4"
/>
<v-banner
v-show="tempSettingsStruct.matchCamerasOnlyByPath"
rounded
color="red"
class="mb-3"
text-color="white"
icon="mdi-information-outline"
>
Physical cameras will be strictly matched to camera configurations using physical USB port they are plugged
into, in addition to device name and other USB metadata. Additionally, no new cameras are allowed to be added.
This setting is useful for guaranteeing that an already known and configured camera can never be matched as an
"unknown"/"new" camera, which resets pipelines and calibration data.
<p />
Cameras will NOT be matched if they change USB ports, and new cameras plugged into this coprocessor will NOT
be automatically recognized or configured for vision processing.
<p />
To add a new camera to this coprocessor, disable this setting, connect the camera, and re-enable.
</v-banner>
<v-divider class="mb-3" />
</v-form>
<v-btn
color="accent"

View File

@@ -44,9 +44,7 @@ export const useSettingsStore = defineStore("settings", {
connName: "Example Wired Connection",
devName: "eth0"
}
],
networkingDisabled: false,
matchCamerasOnlyByPath: false
]
},
lighting: {
supported: true,

View File

@@ -47,7 +47,6 @@ export interface NetworkSettings {
setDHCPcommand?: string;
networkInterfaceNames: NetworkInterfaceType[];
networkingDisabled: boolean;
matchCamerasOnlyByPath: boolean;
}
export type ConfigurableNetworkSettings = Omit<

View File

@@ -23,7 +23,6 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
@@ -52,12 +51,6 @@ public class CameraConfiguration {
@JsonIgnore public String[] otherPaths = {};
@JsonProperty("usbVID")
public int usbVID = -1;
@JsonProperty("usbPID")
public int usbPID = -1;
public CameraType cameraType = CameraType.UsbCamera;
public double FOV = 70;
public final List<CameraCalibrationCoefficients> calibrations;
@@ -105,9 +98,7 @@ public class CameraConfiguration {
@JsonProperty("cameraType") CameraType cameraType,
@JsonProperty("cameraQuirks") QuirkyCamera cameraQuirks,
@JsonProperty("calibration") List<CameraCalibrationCoefficients> calibrations,
@JsonProperty("currentPipelineIndex") int currentPipelineIndex,
@JsonProperty("usbVID") int usbVID,
@JsonProperty("usbPID") int usbPID) {
@JsonProperty("currentPipelineIndex") int currentPipelineIndex) {
this.baseName = baseName;
this.uniqueName = uniqueName;
this.nickname = nickname;
@@ -117,8 +108,6 @@ public class CameraConfiguration {
this.cameraQuirks = cameraQuirks;
this.calibrations = calibrations != null ? calibrations : new ArrayList<>();
this.currentPipelineIndex = currentPipelineIndex;
this.usbPID = usbPID;
this.usbVID = usbVID;
logger.debug(
"Creating camera configuration for "
@@ -167,17 +156,6 @@ public class CameraConfiguration {
calibrations.add(calibration);
}
/**
* Get a unique descriptor of the USB port this camera is attached to. EG
* "/dev/v4l/by-path/platform-fc800000.usb-usb-0:1.3:1.0-video-index0"
*
* @return
*/
@JsonIgnore
public Optional<String> getUSBPath() {
return Arrays.stream(otherPaths).filter(path -> path.contains("/by-path/")).findFirst();
}
@Override
public String toString() {
return "CameraConfiguration [baseName="

View File

@@ -20,24 +20,6 @@ package org.photonvision.common.configuration;
public class HardwareSettings {
public int ledBrightnessPercentage = 100;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ledBrightnessPercentage;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
HardwareSettings other = (HardwareSettings) obj;
if (ledBrightnessPercentage != other.ledBrightnessPercentage) return false;
return true;
}
@Override
public String toString() {
return "HardwareSettings [ledBrightnessPercentage=" + ledBrightnessPercentage + "]";

View File

@@ -39,14 +39,6 @@ public class NetworkConfig {
public boolean shouldManage;
public boolean shouldPublishProto = false;
/**
* If we should ONLY match cameras by path, and NEVER only by base-name. For now default to false
* to preserve old matching logic.
*
* <p>This also disables creating new CameraConfigurations for detected "new" cameras.
*/
public boolean matchCamerasOnlyByPath = false;
@JsonIgnore public static final String NM_IFACE_STRING = "${interface}";
@JsonIgnore public static final String NM_IP_STRING = "${ipaddr}";
@@ -84,8 +76,7 @@ public class NetworkConfig {
@JsonProperty("shouldPublishProto") boolean shouldPublishProto,
@JsonProperty("networkManagerIface") String networkManagerIface,
@JsonProperty("setStaticCommand") String setStaticCommand,
@JsonProperty("setDHCPcommand") String setDHCPcommand,
@JsonProperty("matchCamerasOnlyByPath") boolean matchCamerasOnlyByPath) {
@JsonProperty("setDHCPcommand") String setDHCPcommand) {
this.ntServerAddress = ntServerAddress;
this.connectionType = connectionType;
this.staticIp = staticIp;
@@ -95,7 +86,6 @@ public class NetworkConfig {
this.networkManagerIface = networkManagerIface;
this.setStaticCommand = setStaticCommand;
this.setDHCPcommand = setDHCPcommand;
this.matchCamerasOnlyByPath = matchCamerasOnlyByPath;
setShouldManage(shouldManage);
}

View File

@@ -23,6 +23,5 @@ public enum LogGroup {
VisionModule,
Data,
General,
Config,
CSCore,
Config
}

View File

@@ -99,7 +99,6 @@ public class Logger {
levelMap.put(LogGroup.Data, LogLevel.INFO);
levelMap.put(LogGroup.VisionModule, LogLevel.INFO);
levelMap.put(LogGroup.Config, LogLevel.INFO);
levelMap.put(LogGroup.CSCore, LogLevel.TRACE);
}
static {
@@ -195,7 +194,7 @@ public class Logger {
return logLevel.code <= levelMap.get(group).code;
}
void log(String message, LogLevel level) {
private void log(String message, LogLevel level) {
if (shouldLog(level)) {
log(message, level, group, className);
}

View File

@@ -1,70 +0,0 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.common.logging;
import edu.wpi.first.cscore.CameraServerJNI;
import java.nio.file.Path;
/** Redirect cscore logs to our logger */
public class PvCSCoreLogger {
private static PvCSCoreLogger INSTANCE;
public static PvCSCoreLogger getInstance() {
if (INSTANCE == null) {
INSTANCE = new PvCSCoreLogger();
}
return INSTANCE;
}
private Logger logger;
private PvCSCoreLogger() {
CameraServerJNI.setLogger(this::logMsg, 7);
this.logger = new Logger(getClass(), LogGroup.CSCore);
}
private void logMsg(int level, String file, int line, String msg) {
if (level == 20) {
logger.info(msg);
return;
}
file = Path.of(file).getFileName().toString();
String levelmsg;
LogLevel pvlevel;
if (level >= 50) {
levelmsg = "CRITICAL";
pvlevel = LogLevel.ERROR;
} else if (level >= 40) {
levelmsg = "ERROR";
pvlevel = LogLevel.ERROR;
} else if (level >= 30) {
levelmsg = "WARNING";
pvlevel = LogLevel.WARN;
} else if (level >= 20) {
levelmsg = "INFO";
pvlevel = LogLevel.INFO;
} else {
levelmsg = "DEBUG";
pvlevel = LogLevel.DEBUG;
}
logger.log(
"CS: " + levelmsg + " " + level + ": " + msg + " (" + file + ":" + line + ")", pvlevel);
}
}

View File

@@ -53,7 +53,6 @@ public class MrCalJNILoader extends PhotonJNICommon {
"libcolamd",
"libccolamd",
"openblas",
"libwinpthread-1",
"libgcc_s_seh-1",
"libquadmath-0",
"libgfortran-5",

View File

@@ -19,8 +19,6 @@ package org.photonvision.vision.camera;
import edu.wpi.first.cscore.UsbCameraInfo;
import java.util.Arrays;
import java.util.Optional;
import org.photonvision.common.hardware.Platform;
public class CameraInfo extends UsbCameraInfo {
public final CameraType cameraType;
@@ -70,54 +68,15 @@ public class CameraInfo extends UsbCameraInfo {
return getBaseName().replaceAll(" ", "_");
}
/**
* Get a unique descriptor of the USB port this camera is attached to. EG
* "/dev/v4l/by-path/platform-fc800000.usb-usb-0:1.3:1.0-video-index0"
*
* @return
*/
public Optional<String> getUSBPath() {
return Arrays.stream(otherPaths).filter(path -> path.contains("/by-path/")).findFirst();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
CameraInfo other = (CameraInfo) obj;
// Windows device number is not significant. See
// https://github.com/wpilibsuite/allwpilib/blob/4b94a64b06057c723d6fcafeb1a45f55a70d179a/cscore/src/main/native/windows/UsbCameraImpl.cpp#L1128
if (!Platform.isWindows()) {
if (dev != other.dev) return false;
}
if (!path.equals(other.path)) return false;
if (!name.equals(other.name)) return false;
if (!Arrays.asList(this.otherPaths).containsAll(Arrays.asList(other.otherPaths))) return false;
if (vendorId != other.vendorId) return false;
if (productId != other.productId) return false;
// Don't trust super.equals, as it compares references. Should PR this to allwpilib at some
// point
return true;
}
@Override
public String toString() {
return "CameraInfo [cameraType="
+ cameraType
+ ", baseName="
+ getBaseName()
+ ", vid="
+ vendorId
+ ", pid="
+ productId
+ ", path="
+ path
+ ", otherPaths="
+ Arrays.toString(otherPaths)
+ "]";
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof UsbCameraInfo || o instanceof CameraInfo)) return false;
UsbCameraInfo other = (UsbCameraInfo) o;
return path.equals(other.path)
// && a.dev == b.dev (dev is not constant in Windows)
&& name.equals(other.name)
&& productId == other.productId
&& vendorId == other.vendorId;
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.vision.camera;
import java.util.*;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.vision.frame.Frame;
import org.photonvision.vision.frame.FrameProvider;
import org.photonvision.vision.frame.FrameThresholdType;
import org.photonvision.vision.opencv.ImageRotationMode;
import org.photonvision.vision.pipe.impl.HSVPipe.HSVParams;
import org.photonvision.vision.processes.VisionSource;
import org.photonvision.vision.processes.VisionSourceSettables;
/** Dummy class for unit testing the vision source manager */
public class TestSource extends VisionSource {
private FrameProvider usbFrameProvider;
public TestSource(CameraConfiguration config) {
super(config);
if (getCameraConfiguration().cameraQuirks == null)
getCameraConfiguration().cameraQuirks =
QuirkyCamera.getQuirkyCamera(config.usbVID, config.usbVID, config.baseName);
}
@Override
public FrameProvider getFrameProvider() {
return new FrameProvider() {
@Override
public Frame get() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'get'");
}
@Override
public String getName() {
return cameraConfiguration.uniqueName;
}
@Override
public void requestFrameThresholdType(FrameThresholdType type) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'requestFrameThresholdType'");
}
@Override
public void requestFrameRotation(ImageRotationMode rotationMode) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'requestFrameRotation'");
}
@Override
public void requestFrameCopies(boolean copyInput, boolean copyOutput) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'requestFrameCopies'");
}
@Override
public void requestHsvSettings(HSVParams params) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'requestHsvSettings'");
}
};
}
@Override
public VisionSourceSettables getSettables() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getSettables'");
}
@Override
public boolean isVendorCamera() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'isVendorCamera'");
}
}

View File

@@ -49,17 +49,9 @@ public class USBCameraSource extends VisionSource {
super(config);
logger = new Logger(USBCameraSource.class, config.nickname, LogGroup.Camera);
// cscore will auto-reconnect to the camera path we give it. v4l does not guarantee that if i
// swap cameras around, the same /dev/videoN ID will be assigned to that camera. So instead
// default to pinning to a particular USB port, or by "path" (appears to be a global identifier)
// on Windows.
camera = new UsbCamera(config.nickname, config.getUSBPath().orElse(config.path));
camera = new UsbCamera(config.nickname, config.path);
cvSink = CameraServer.getVideo(this.camera);
// set vid/pid if not done already for future matching
if (config.usbVID <= 0) config.usbVID = this.camera.getInfo().vendorId;
if (config.usbPID <= 0) config.usbPID = this.camera.getInfo().productId;
if (getCameraConfiguration().cameraQuirks == null)
getCameraConfiguration().cameraQuirks =
QuirkyCamera.getQuirkyCamera(

View File

@@ -1,57 +0,0 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.photonvision.vision.processes;
import java.util.List;
import org.photonvision.vision.camera.CameraType;
public class CameraMatchingOptions {
public CameraMatchingOptions(
boolean checkUSBPath,
boolean checkVidPid,
boolean checkBaseName,
boolean checkPath,
CameraType... allowedTypes) {
this.checkUSBPath = checkUSBPath;
this.checkVidPid = checkVidPid;
this.checkBaseName = checkBaseName;
this.checkPath = checkPath;
this.allowedTypes = List.of(allowedTypes);
}
public final boolean checkUSBPath;
public final boolean checkVidPid;
public final boolean checkBaseName;
public final boolean checkPath;
public final List<CameraType> allowedTypes;
@Override
public String toString() {
return "CameraMatchingOptions [checkUSBPath="
+ checkUSBPath
+ ", checkVidPid="
+ checkVidPid
+ ", checkBaseName="
+ checkBaseName
+ ", checkPath="
+ checkPath
+ ", allowedTypes="
+ allowedTypes
+ "]";
}
}

View File

@@ -23,7 +23,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.ConfigManager;
@@ -39,7 +38,6 @@ import org.photonvision.vision.camera.CameraInfo;
import org.photonvision.vision.camera.CameraQuirk;
import org.photonvision.vision.camera.CameraType;
import org.photonvision.vision.camera.LibcameraGpuSource;
import org.photonvision.vision.camera.TestSource;
import org.photonvision.vision.camera.USBCameraSource;
public class VisionSourceManager {
@@ -147,8 +145,8 @@ public class VisionSourceManager {
}
// Return no new sources because there are no new sources
if (connectedCameras.isEmpty()) {
if (!hasWarnedNoCameras) {
if (connectedCameras.isEmpty() && !cameraInfos.isEmpty()) {
if (hasWarnedNoCameras) {
logger.warn(
"No cameras were detected! Check that all cameras are connected, and that the path is correct.");
hasWarnedNoCameras = true;
@@ -166,7 +164,7 @@ public class VisionSourceManager {
// Debug prints
for (var info : connectedCameras) {
logger.info("Detected unmatched physical camera: " + info.toString());
logger.info("Adding local video device - \"" + info.name + "\" at \"" + info.path + "\"");
}
if (!unmatchedLoadedConfigs.isEmpty())
@@ -187,7 +185,7 @@ public class VisionSourceManager {
"Unloaded configs: "
+ unmatchedLoadedConfigs.stream()
.map(it -> it.nickname)
.collect(Collectors.joining(", ")));
.collect(Collectors.joining()));
hasWarned = true;
}
@@ -196,8 +194,13 @@ public class VisionSourceManager {
if (matchedCameras.isEmpty()) return null;
// for unit tests only!
if (!createSources) {
return List.of();
}
// Turn these camera configs into vision sources
var sources = loadVisionSourcesFromCamConfigs(matchedCameras, createSources);
var sources = loadVisionSourcesFromCamConfigs(matchedCameras);
// Print info about each vision source
for (var src : sources) {
@@ -213,54 +216,6 @@ public class VisionSourceManager {
return sources;
}
/**
* Get a predicate for checking cameras against a saved config.
*
* @param savedConfig The saved camera configuration to match against
* @param checkUSBPath If we should compare the USB port/bus IDs
* @param checkVidPid If we should compare USB VID and PID
* @param checkBaseName If we should compare {@link CameraInfo#getBaseName}
* @param checkPath If we should check {@link CameraInfo::path} (eg /dev/videoN on Linux, or
* ?/usb#vid_05c8&pid_03df&mi_00#7&fa76035&0&0000#{e5323777-f976-4f5b-9b55-b94699c46e44}\global
* on Windows)
*/
private final Predicate<CameraInfo> getCameraMatcher(
final CameraConfiguration savedConfig,
boolean checkUSBPath,
boolean checkVidPid,
boolean checkBaseName,
boolean checkPath) {
if (checkUSBPath && savedConfig.getUSBPath().isEmpty()) {
logger.debug(
"WARN: Camera has empty USB path, but asked to match by name: "
+ camCfgToString(savedConfig));
}
return (CameraInfo physicalCamera) -> {
var matches = true;
if (checkUSBPath) {
var savedPath = savedConfig.getUSBPath();
matches &= (savedPath.isPresent() && physicalCamera.getUSBPath().equals(savedPath));
}
if (checkBaseName) {
matches &= physicalCamera.getBaseName().equals(savedConfig.baseName);
}
if (checkVidPid) {
matches &=
(physicalCamera.vendorId == savedConfig.usbVID
&& physicalCamera.productId == savedConfig.usbPID);
}
if (checkPath) {
matches &= (physicalCamera.path.equals(savedConfig.path));
}
matches &= (physicalCamera.cameraType == savedConfig.cameraType);
return matches;
};
}
/**
* Create {@link CameraConfiguration}s based on a list of detected USB cameras and the configs on
* disk.
@@ -271,212 +226,158 @@ public class VisionSourceManager {
*/
public List<CameraConfiguration> matchCameras(
List<CameraInfo> detectedCamInfos, List<CameraConfiguration> loadedCamConfigs) {
return matchCameras(
detectedCamInfos,
loadedCamConfigs,
ConfigManager.getInstance().getConfig().getNetworkConfig().matchCamerasOnlyByPath);
}
private static final String camCfgToString(CameraConfiguration c) {
return new StringBuilder()
.append("[baseName=")
.append(c.baseName)
.append(", uniqueName=")
.append(c.uniqueName)
.append(", otherPaths=")
.append(Arrays.toString(c.otherPaths))
.append(", vid=")
.append(c.usbVID)
.append(", pid=")
.append(c.usbPID)
.append("]")
.toString();
}
/**
* Create {@link CameraConfiguration}s based on a list of detected USB cameras and the configs on
* disk.
*
* @param detectedCamInfos Information about currently connected USB cameras.
* @param loadedCamConfigs The USB {@link CameraConfiguration}s loaded from disk.
* @param matchCamerasOnlyByPath If we should never try to match only by (base name, vid, pid)
* @return the matched configurations.
*/
public List<CameraConfiguration> matchCameras(
List<CameraInfo> detectedCamInfos,
List<CameraConfiguration> loadedCamConfigs,
boolean matchCamerasOnlyByPath) {
var detectedCameraList = new ArrayList<>(detectedCamInfos);
ArrayList<CameraConfiguration> cameraConfigurations = new ArrayList<CameraConfiguration>();
ArrayList<CameraConfiguration> unloadedConfigs =
new ArrayList<CameraConfiguration>(loadedCamConfigs);
logger.info("Matching CSI cameras by port & base name...");
cameraConfigurations.addAll(
matchCamerasByStrategy(
detectedCameraList,
unloadedConfigs,
new CameraMatchingOptions(false, false, true, true, CameraType.ZeroCopyPicam)));
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0)
cameraConfigurations.addAll(matchByPathByID(detectedCameraList, unloadedConfigs));
else logger.debug("Skipping matchByPath no configs or cameras left to match");
logger.info("Matching USB cameras by usb port & name & USB VID/PID...");
cameraConfigurations.addAll(
matchCamerasByStrategy(
detectedCameraList,
unloadedConfigs,
new CameraMatchingOptions(true, true, true, false, CameraType.UsbCamera)));
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0)
cameraConfigurations.addAll(matchByPath(detectedCameraList, unloadedConfigs));
else logger.debug("Skipping matchByPath no configs or cameras left to match");
// On windows, the v4l path is actually useful and tells us the port the camera is physically
// connected to which is neat
if (Platform.isWindows() && !matchCamerasOnlyByPath) {
logger.info("Matching USB cameras by windows-path & USB VID/PID only...");
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0)
cameraConfigurations.addAll(matchByName(detectedCameraList, unloadedConfigs));
else logger.debug("Skipping matchByName no configs or cameras left to match");
if (detectedCameraList.size() > 0)
cameraConfigurations.addAll(
matchCamerasByStrategy(
detectedCameraList,
unloadedConfigs,
new CameraMatchingOptions(false, true, true, true, CameraType.UsbCamera)));
}
logger.info("Matching USB cameras by usb port & USB VID/PID...");
cameraConfigurations.addAll(
matchCamerasByStrategy(
detectedCameraList,
unloadedConfigs,
new CameraMatchingOptions(true, true, false, false, CameraType.UsbCamera)));
// Legacy migration -- VID/PID will be unset, so we have to try with our most relaxed strategy
// at least once. We _should_ still have a valid USB path (assuming cameras have not moved), so
// try that first, then fallback to base name only beloow
logger.info("Matching USB cameras by base-name & usb port...");
cameraConfigurations.addAll(
matchCamerasByStrategy(
detectedCameraList,
unloadedConfigs,
new CameraMatchingOptions(true, false, true, false, CameraType.UsbCamera)));
// handle disabling only-by-base-name matching
if (!matchCamerasOnlyByPath) {
logger.info("Matching USB cameras by base-name & USB VID/PID only...");
cameraConfigurations.addAll(
matchCamerasByStrategy(
detectedCameraList,
unloadedConfigs,
new CameraMatchingOptions(false, true, true, false, CameraType.UsbCamera)));
// Legacy migration for if no USB VID/PID set
logger.info("Matching USB cameras by base-name only...");
cameraConfigurations.addAll(
matchCamerasByStrategy(
detectedCameraList,
unloadedConfigs,
new CameraMatchingOptions(false, false, true, false, CameraType.UsbCamera)));
} else logger.info("Skipping match by filepath/vid/pid, disabled by user");
if (detectedCameraList.size() > 0) {
// handle disabling only-by-base-name matching
if (!matchCamerasOnlyByPath) {
cameraConfigurations.addAll(
createConfigsForCameras(detectedCameraList, unloadedConfigs, cameraConfigurations));
} else {
logger.warn(
"Not creating 'new' Photon CameraConfigurations for ["
+ detectedCamInfos.stream()
.map(CameraInfo::toString)
.collect(Collectors.joining(";"))
+ "], disabled by user");
}
}
createConfigsForCameras(detectedCameraList, unloadedConfigs, cameraConfigurations));
logger.debug("Matched or created " + cameraConfigurations.size() + " camera configs!");
return cameraConfigurations;
}
/**
* Abstractly match cameras
*
* @param detectedCamInfos Physical cameras unmatched and attached to the device
* @param unloadedConfigs {@link CameraConfiguration}
* @param checkUSBPath If we should compare the USB port/bus IDs
* @param checkVidPid If we should compare USB VID and PID
* @param checkBaseName If we should check {@link CameraInfo::getBaseName}
* @param checkPath If we should check {@link CameraInfo::path} (eg /dev/videoN on Linux, or
* usb#vid_05c8&pid_03df&mi_00#7&fa76035&0&0000#{e5323777-f976-4f5b-9b55-b94699c46e44}\global
* on Windows). Note that path may change based on order cameras are plugged in/unplugged on
* Linux, and should not be trusted to remain the same.
* @return All matched or created new configs
*/
private List<CameraConfiguration> matchCamerasByStrategy(
List<CameraInfo> detectedCamInfos,
List<CameraConfiguration> unloadedConfigs,
CameraMatchingOptions matchingOptions) {
// loop over all the configs loaded from disk, attempting to match each camera
// to a config by path-by-id on linux
private List<CameraConfiguration> matchByPathByID(
List<CameraInfo> detectedCamInfos, List<CameraConfiguration> unloadedConfigs) {
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
List<CameraConfiguration> unloadedConfigsCopy =
new ArrayList<CameraConfiguration>(unloadedConfigs);
if (unloadedConfigsCopy.isEmpty()) return List.of();
logger.debug("Matching with options " + matchingOptions.toString());
for (CameraConfiguration config : unloadedConfigsCopy) {
// Only run match path by id if the camera type is allowed. This allows us to specify matching
// behavior per-camera-type
if (matchingOptions.allowedTypes.contains(config.cameraType)) {
logger.debug(
String.format(
"Trying to find a match for loaded camera %s (%s) with camera config: %s",
config.baseName, config.uniqueName, camCfgToString(config)));
// Get matcher and filter against it, picking out the first match
Predicate<CameraInfo> matches =
getCameraMatcher(
config,
matchingOptions.checkUSBPath,
matchingOptions.checkVidPid,
matchingOptions.checkBaseName,
matchingOptions.checkPath);
var cameraInfo = detectedCamInfos.stream().filter(matches).findFirst().orElse(null);
// If we actually matched a camera to a config, remove that camera from the list
// and add it to the output
if (cameraInfo != null) {
logger.debug(
"Matched the config for "
+ config.uniqueName
+ " to the physical camera config above!");
ret.add(mergeInfoIntoConfig(config, cameraInfo));
detectedCamInfos.remove(cameraInfo);
unloadedConfigs.remove(config);
// Only run match path by id if the camera is not a CSI camera.
if (config.cameraType != CameraType.ZeroCopyPicam) {
CameraInfo cameraInfo;
if (config.otherPaths.length == 0) {
logger.debug("No valid path-by-id found for config with name " + config.baseName);
} else {
logger.debug("No camera found for the config " + config.uniqueName);
// attempt matching by path and basename
logger.debug(
"Trying to find a match for loaded camera "
+ config.baseName
+ " with path-by-id "
+ config.otherPaths[0]);
cameraInfo =
detectedCamInfos.stream()
.filter(
usbCameraInfo ->
usbCameraInfo.otherPaths.length != 0
&& usbCameraInfo.otherPaths[0].equals(config.otherPaths[0])
&& usbCameraInfo.getBaseName().equals(config.baseName))
.findFirst()
.orElse(null);
// If we actually matched a camera to a config, remove that camera from the list
// and add it to the output
if (cameraInfo != null) {
logger.debug("Matched the config for " + config.baseName + " to a physical camera!");
ret.add(mergeInfoIntoConfig(config, cameraInfo));
detectedCamInfos.remove(cameraInfo);
unloadedConfigs.remove(config);
}
}
}
}
return ret;
}
/**
* Create new {@link CameraConfiguration}s for unmatched cameras, and assign them a unique name
* (unique in the set of (loaded configs, unloaded configs, loaded vision modules) at least)
*/
private List<CameraConfiguration> matchByPath(
List<CameraInfo> detectedCamInfos, List<CameraConfiguration> unloadedConfigs) {
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
List<CameraConfiguration> unloadedConfigsCopy =
new ArrayList<CameraConfiguration>(unloadedConfigs);
// now attempt to match the cameras and configs remaining by normal path
for (CameraConfiguration config : unloadedConfigsCopy) {
CameraInfo cameraInfo;
// attempt matching by path and basename
logger.debug(
"Trying to find a match for loaded camera "
+ config.baseName
+ " with path "
+ config.path);
cameraInfo =
detectedCamInfos.stream()
.filter(
usbCameraInfo ->
usbCameraInfo.path.equals(config.path)
&& usbCameraInfo.getBaseName().equals(config.baseName))
.findFirst()
.orElse(null);
// If we actually matched a camera to a config, remove that camera from the list
// and add it to the output
if (cameraInfo != null) {
logger.debug("Matched the config for " + config.baseName + " to a physical camera!");
ret.add(mergeInfoIntoConfig(config, cameraInfo));
detectedCamInfos.remove(cameraInfo);
unloadedConfigs.remove(config);
}
}
return ret;
}
// Try matching cameras to configs by name.
private List<CameraConfiguration> matchByName(
List<CameraInfo> detectedCamInfos, List<CameraConfiguration> unloadedConfigs) {
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
List<CameraConfiguration> unloadedConfigsCopy =
new ArrayList<CameraConfiguration>(unloadedConfigs);
// if both path and ID based matching fails, attempt basename only match
for (CameraConfiguration config : unloadedConfigsCopy) {
CameraInfo cameraInfo;
logger.debug("Trying to find a match for loaded camera with name " + config.baseName);
cameraInfo =
detectedCamInfos.stream()
.filter(CameraInfo -> CameraInfo.getBaseName().equals(config.baseName))
.findFirst()
.orElse(null);
// If we actually matched a camera to a config, remove that camera from the list
// and add it to the output
if (cameraInfo != null) {
logger.debug("Matched the config for " + config.baseName + " to a physical camera!");
ret.add(mergeInfoIntoConfig(config, cameraInfo));
detectedCamInfos.remove(cameraInfo);
unloadedConfigs.remove(config);
}
}
return ret;
}
// If we have any unmatched cameras left, create a new CameraConfiguration for
// them here.
private List<CameraConfiguration> createConfigsForCameras(
List<CameraInfo> detectedCameraList,
List<CameraConfiguration> unloadedCamConfigs,
List<CameraConfiguration> loadedCamConfigs,
List<CameraConfiguration> loadedConfigs) {
List<CameraConfiguration> ret = new ArrayList<CameraConfiguration>();
logger.debug(
"After matching loaded configs, these configs remained unmatched: "
+ detectedCameraList.stream()
.map(n -> String.valueOf(n))
.collect(Collectors.joining("-", "{", "}")));
"After matching loaded configs " + detectedCameraList.size() + " cameras were unmatched.");
for (CameraInfo info : detectedCameraList) {
// create new camera config for all new cameras
String baseName = info.getBaseName();
String uniqueName = info.getHumanReadableName();
int suffix = 0;
while (containsName(loadedConfigs, uniqueName)
|| containsName(uniqueName)
|| containsName(unloadedCamConfigs, uniqueName)
|| containsName(ret, uniqueName)) {
while (containsName(loadedConfigs, uniqueName) || containsName(uniqueName)) {
suffix++;
uniqueName = String.format("%s (%d)", uniqueName, suffix);
}
@@ -556,16 +457,10 @@ public class VisionSourceManager {
}
private static List<VisionSource> loadVisionSourcesFromCamConfigs(
List<CameraConfiguration> camConfigs, boolean createSources) {
List<CameraConfiguration> camConfigs) {
var cameraSources = new ArrayList<VisionSource>();
for (var configuration : camConfigs) {
logger.debug("Creating VisionSource for " + camCfgToString(configuration));
// In unit tests, create dummy
if (!createSources) {
cameraSources.add(new TestSource(configuration));
continue;
}
logger.debug("Creating VisionSource for " + configuration);
boolean is_pi = Platform.isRaspberryPi();

View File

@@ -139,31 +139,8 @@ public class ConfigTest {
writer.write(str);
writer.flush();
writer.close();
CameraConfiguration result =
JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class);
tempFile.delete();
}
@Test
public void testJacksonAddUSBVIDPID() throws IOException {
var str =
"{\"baseName\":\"aaaaaa\",\"uniqueName\":\"aaaaaa\",\"nickname\":\"aaaaaa\",\"FOV\":70.0,\"path\":\"dev/vid\",\"cameraType\":\"UsbCamera\",\"currentPipelineIndex\":0,\"camPitch\":{\"radians\":0.0},\"calibrations\":[], \"usbVID\":3, \"usbPID\":4, \"cameraLEDs\":[]}";
File tempFile = File.createTempFile("test", ".json");
tempFile.deleteOnExit();
var writer = new FileWriter(tempFile);
writer.write(str);
writer.flush();
writer.close();
try {
CameraConfiguration result =
JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class);
String ser = JacksonUtils.serializeToString(result);
System.out.println(ser);
} catch (Exception e) {
e.printStackTrace();
}
Assertions.assertDoesNotThrow(
() -> JacksonUtils.deserialize(tempFile.toPath(), CameraConfiguration.class));
tempFile.delete();
}

View File

@@ -84,9 +84,7 @@ public class SQLConfigTest {
CameraType.UsbCamera,
QuirkyCamera.getQuirkyCamera(-1, -1),
List.of(),
0,
-1,
-1);
0);
testcamcfg.pipelineSettings =
List.of(
new ReflectivePipelineSettings(),

View File

@@ -21,24 +21,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.photonvision.common.configuration.CameraConfiguration;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.LogLevel;
import org.photonvision.common.logging.Logger;
import org.photonvision.vision.camera.CameraInfo;
import org.photonvision.vision.camera.CameraType;
public class VisionSourceManagerTest {
@Test
public void visionSourceTest() {
Logger.setLevel(LogGroup.Camera, LogLevel.DEBUG);
var inst = new VisionSourceManager();
var cameraInfos = new ArrayList<CameraInfo>();
ConfigManager.getInstance().clearConfig();
ConfigManager.getInstance().load();
inst.tryMatchCamImpl(cameraInfos);
@@ -50,8 +43,6 @@ public class VisionSourceManagerTest {
"thirdTestVideo",
"dev/video1",
new String[] {"by-id/123"});
config3.usbVID = 3;
config3.usbPID = 4;
var config4 =
new CameraConfiguration(
"fourthTestVideo",
@@ -59,8 +50,6 @@ public class VisionSourceManagerTest {
"fourthTestVideo",
"dev/video2",
new String[] {"by-id/321"});
config4.usbVID = 5;
config4.usbPID = 6;
CameraInfo info1 = new CameraInfo(0, "dev/video0", "testVideo", new String[0], 1, 2);
@@ -272,335 +261,4 @@ public class VisionSourceManagerTest {
assertEquals(10, inst.knownCameras.size());
assertEquals(0, inst.unmatchedLoadedConfigs.size());
}
@Test
public void testDisableInhibitPathChangeIdenticalCams() {
Logger.setLevel(LogGroup.Camera, LogLevel.DEBUG);
var inst = new VisionSourceManager();
ConfigManager.getInstance().clearConfig();
ConfigManager.getInstance().load();
ConfigManager.getInstance().getConfig().getNetworkConfig().matchCamerasOnlyByPath = false;
var CAM2_OLD_PATH =
new String[] {"/dev/v4l/by-path/platform-fc880000.usb-usb-0:1:1.0-video-index0"};
var CAM2_NEW_PATH =
new String[] {"/dev/v4l/by-path/platform-fc880080.usb-usb-0:1:1.3-video-index0"};
var CAM1_OLD_PATHS =
new String[] {
"/dev/v4l/by-id/usb-Arducam_Technology_Co.__Ltd._Arducam_OV2311_USB_Camera_UC621-video-index0",
"/dev/v4l/by-path/platform-fc800000.usb-usb-0:1:1.0-video-index0"
};
var camera1_saved_config =
new CameraConfiguration(
"Arducam OV2311 USB Camera",
"Arducam OV2311 USB Camera",
"fromt-left",
"/dev/video0",
CAM1_OLD_PATHS);
camera1_saved_config.usbVID = 3141;
camera1_saved_config.usbPID = 25446;
var camera2_saved_config =
new CameraConfiguration(
"Arducam OV2311 USB Camera",
"Arducam OV2311 USB Camera (1)",
"fromt-left",
"/dev/video2",
CAM2_OLD_PATH);
camera2_saved_config.usbVID = 3141;
camera2_saved_config.usbPID = 25446;
// And load our "old" configs
inst.registerLoadedConfigs(camera1_saved_config, camera2_saved_config);
// Camera attached to new port, but strict matching disabled
{
CameraInfo info1 =
new CameraInfo(
0, "/dev/video11", "Arducam OV2311 USB Camera", CAM1_OLD_PATHS, 3141, 25446);
CameraInfo info2 =
new CameraInfo(
0, "/dev/video12", "Arducam OV2311 USB Camera", CAM2_NEW_PATH, 3141, 25446);
var cameraInfos = new ArrayList<CameraInfo>();
cameraInfos.add(info1);
cameraInfos.add(info2);
List<VisionSource> ret1 = inst.tryMatchCamImpl(cameraInfos);
// and check the new one got matched got matched
assertEquals(2, ret1.size());
assertEquals(
1, ret1.stream().filter(it -> it.cameraConfiguration.path.equals(info1.path)).count());
assertEquals(
1, ret1.stream().filter(it -> it.cameraConfiguration.path.equals(info2.path)).count());
}
}
@Test
public void testInhibitPathChangeIdenticalCams() {
Logger.setLevel(LogGroup.Camera, LogLevel.DEBUG);
var inst = new VisionSourceManager();
ConfigManager.getInstance().clearConfig();
ConfigManager.getInstance().load();
ConfigManager.getInstance().getConfig().getNetworkConfig().matchCamerasOnlyByPath = true;
var CAM2_OLD_PATH =
new String[] {"/dev/v4l/by-path/platform-fc880000.usb-usb-0:1:1.0-video-index0"};
var CAM2_NEW_PATH =
new String[] {"/dev/v4l/by-path/platform-fc880080.usb-usb-0:1:1.3-video-index0"};
var CAM1_OLD_PATHS =
new String[] {
"/dev/v4l/by-id/usb-Arducam_Technology_Co.__Ltd._Arducam_OV2311_USB_Camera_UC621-video-index0",
"/dev/v4l/by-path/platform-fc800000.usb-usb-0:1:1.0-video-index0"
};
var camera1_saved_config =
new CameraConfiguration(
"Arducam OV2311 USB Camera",
"Arducam OV2311 USB Camera (1)",
"fromt-left",
"/dev/video0",
CAM1_OLD_PATHS);
camera1_saved_config.usbVID = 3141;
camera1_saved_config.usbPID = 25446;
var camera2_saved_config =
new CameraConfiguration(
"Arducam OV2311 USB Camera",
"Arducam OV2311 USB Camera (1)",
"fromt-left",
"/dev/video2",
CAM2_OLD_PATH);
camera2_saved_config.usbVID = 3141;
camera2_saved_config.usbPID = 25446;
// And load our "old" configs
inst.registerLoadedConfigs(camera1_saved_config, camera2_saved_config);
// initial pass with camera in the wrong spot
{
// Give our cameras new "paths" to fake the windows logic out. this should not
// affect strict matching
CameraInfo info1 =
new CameraInfo(
0, "/dev/video11", "Arducam OV2311 USB Camera", CAM1_OLD_PATHS, 3141, 25446);
CameraInfo info2 =
new CameraInfo(
0, "/dev/video12", "Arducam OV2311 USB Camera", CAM2_NEW_PATH, 3141, 25446);
var cameraInfos = new ArrayList<CameraInfo>();
cameraInfos.add(info1);
cameraInfos.add(info2);
List<VisionSource> ret1 = inst.tryMatchCamImpl(cameraInfos);
// Our cameras should be "known"
assertTrue(inst.knownCameras.contains(info1));
assertTrue(inst.knownCameras.contains(info2));
assertEquals(2, inst.knownCameras.size());
// And we should have matched one camera
assertEquals(1, ret1.size());
// and only matched camera1, not 2
assertEquals(
1, ret1.stream().filter(it -> it.cameraConfiguration.path.equals(info1.path)).count());
assertEquals(
0, ret1.stream().filter(it -> it.cameraConfiguration.path.equals(info2.path)).count());
}
// Now move our camera back
{
CameraInfo info1 =
new CameraInfo(
0, "/dev/video11", "Arducam OV2311 USB Camera", CAM1_OLD_PATHS, 3141, 25446);
CameraInfo info2 =
new CameraInfo(
0, "/dev/video12", "Arducam OV2311 USB Camera", CAM2_OLD_PATH, 3141, 25446);
var cameraInfos = new ArrayList<CameraInfo>();
cameraInfos.add(info1);
cameraInfos.add(info2);
List<VisionSource> ret1 = inst.tryMatchCamImpl(cameraInfos);
// and check the new one got matched got matched
assertEquals(1, ret1.size());
assertEquals(
0, ret1.stream().filter(it -> it.cameraConfiguration.path.equals(info1.path)).count());
assertEquals(
1, ret1.stream().filter(it -> it.cameraConfiguration.path.equals(info2.path)).count());
}
}
@Test
public void testCSICameraMatching() {
Logger.setLevel(LogGroup.Camera, LogLevel.DEBUG);
// List of known cameras
var cameraInfos = new ArrayList<CameraInfo>();
var inst = new VisionSourceManager();
ConfigManager.getInstance().clearConfig();
ConfigManager.getInstance().load();
ConfigManager.getInstance().getConfig().getNetworkConfig().matchCamerasOnlyByPath = false;
CameraInfo info1 =
new CameraInfo(
-1,
"/base/soc/i2c0mux/i2c@0/ov9281@60",
"OV9281", // Typically rp1-cfe for unit test changed to CSICAM-DEV
new String[] {},
-1,
-1,
CameraType.ZeroCopyPicam);
CameraInfo info2 =
new CameraInfo(
-1,
"/base/soc/i2c0mux/i2c@1/ov9281@60",
"OV9281", // Typically rp1-cfe for unit test changed to CSICAM-DEV
new String[] {},
-1,
-1,
CameraType.ZeroCopyPicam);
var camera1_saved_config =
new CameraConfiguration(
"OV9281", "OV9281", "test-1", "/base/soc/i2c0mux/i2c@0/ov9281@60", new String[0]);
camera1_saved_config.cameraType = CameraType.ZeroCopyPicam;
camera1_saved_config.usbVID = -1;
camera1_saved_config.usbPID = -1;
var camera2_saved_config =
new CameraConfiguration(
"OV9281", "OV9281 (1)", "test-2", "/base/soc/i2c0mux/i2c@1/ov9281@60", new String[0]);
camera2_saved_config.usbVID = -1;
camera2_saved_config.usbPID = -1;
camera2_saved_config.cameraType = CameraType.ZeroCopyPicam;
cameraInfos.add(info1);
cameraInfos.add(info2);
// Try matching with both cameras being "known"
inst.registerLoadedConfigs(camera1_saved_config, camera2_saved_config);
var ret1 = inst.tryMatchCamImpl(cameraInfos);
// Our cameras should be "known"
assertTrue(inst.knownCameras.contains(info1));
assertTrue(inst.knownCameras.contains(info2));
assertEquals(2, inst.knownCameras.size());
assertEquals(2, ret1.size());
// Exactly one camera should have the path we put in
for (int i = 0; i < cameraInfos.size(); i++) {
var testPath = cameraInfos.get(i).path;
assertEquals(
1, ret1.stream().filter(it -> testPath.equals(it.cameraConfiguration.path)).count());
}
}
@Test
public void testIdenticalCameras() {
Logger.setLevel(LogGroup.Camera, LogLevel.DEBUG);
// List of known cameras
var cameraInfos = new ArrayList<CameraInfo>();
var inst = new VisionSourceManager();
ConfigManager.getInstance().clearConfig();
ConfigManager.getInstance().load();
ConfigManager.getInstance().getConfig().getNetworkConfig().matchCamerasOnlyByPath = false;
// Match empty camera infos
inst.tryMatchCamImpl(cameraInfos);
CameraInfo info1 =
new CameraInfo(
0,
"/dev/video0",
"Arducam OV2311 USB Camera",
new String[] {
"/dev/v4l/by-id/usb-Arducam_Technology_Co.__Ltd._Arducam_OV2311_USB_Camera_UC621-video-index0",
"/dev/v4l/by-path/platform-fc800000.usb-usb-0:1:1.0-video-index0"
},
3141,
25446);
CameraInfo info2 =
new CameraInfo(
0,
"/dev/video2",
"Arducam OV2311 USB Camera",
new String[] {
"/dev/v4l/by-id/usb-Arducam_Technology_Co.__Ltd._Arducam_OV2311_USB_Camera_UC621-video-index0",
"/dev/v4l/by-path/platform-fc880000.usb-usb-0:1:1.0-video-index0"
},
3141,
25446);
cameraInfos.add(info1);
cameraInfos.add(info2);
// Match two "new" cameras
var ret1 = inst.tryMatchCamImpl(cameraInfos);
// Our cameras should be "known"
assertTrue(inst.knownCameras.contains(info1));
assertTrue(inst.knownCameras.contains(info2));
assertEquals(2, inst.knownCameras.size());
assertEquals(2, ret1.size());
// Exactly one camera should have the path we put in
for (int i = 0; i < cameraInfos.size(); i++) {
var testPath = cameraInfos.get(i).getUSBPath().get();
assertEquals(
1,
ret1.stream()
.filter(it -> testPath.equals(it.cameraConfiguration.getUSBPath().get()))
.count());
}
// and the names should be unique
for (int i = 0; i < ret1.size(); i++) {
var thisName = ret1.get(i).cameraConfiguration.uniqueName;
assertEquals(
1,
ret1.stream().filter(it -> thisName.equals(it.cameraConfiguration.uniqueName)).count());
}
// duplciate cameras, same info, new ref
var duplicateCameraInfos = new ArrayList<CameraInfo>();
CameraInfo info1_dup =
new CameraInfo(
0,
"/dev/video0",
"Arducam OV2311 USB Camera",
new String[] {
"/dev/v4l/by-id/usb-Arducam_Technology_Co.__Ltd._Arducam_OV2311_USB_Camera_UC621-video-index0",
"/dev/v4l/by-path/platform-fc800000.usb-usb-0:1:1.0-video-index0"
},
3141,
25446);
CameraInfo info2_dup =
new CameraInfo(
0,
"/dev/video2",
"Arducam OV2311 USB Camera",
new String[] {
"/dev/v4l/by-id/usb-Arducam_Technology_Co.__Ltd._Arducam_OV2311_USB_Camera_UC621-video-index0",
"/dev/v4l/by-path/platform-fc880000.usb-usb-0:1:1.0-video-index0"
},
3141,
25446);
duplicateCameraInfos.add(info1_dup);
duplicateCameraInfos.add(info2_dup);
inst.tryMatchCamImpl(duplicateCameraInfos);
// Our cameras should be "known", and we should only "know" two cameras still
assertTrue(inst.knownCameras.contains(info1_dup));
assertTrue(inst.knownCameras.contains(info2_dup));
assertEquals(2, inst.knownCameras.size());
}
}

View File

@@ -90,7 +90,7 @@ class PhotonCamera:
retVal.populateFromPacket(pkt)
# NT4 allows us to correct the timestamp based on when the message was sent
retVal.setTimestampSeconds(
timestamp / 1e6 - retVal.getLatencyMillis() / 1e3
timestamp / 1e-6 - retVal.getLatencyMillis() / 1e-3
)
return retVal

View File

@@ -431,7 +431,7 @@ public class PhotonCameraSim implements AutoCloseable {
detectableTgts.add(
new PhotonTrackedTarget(
-Math.toDegrees(centerRot.getZ()),
Math.toDegrees(centerRot.getZ()),
-Math.toDegrees(centerRot.getY()),
areaPercent,
Math.toDegrees(centerRot.getX()),

View File

@@ -260,7 +260,7 @@ class PhotonCameraSim {
std::vector<std::pair<double, double>> cornersDouble{cornersFloat.begin(),
cornersFloat.end()};
detectableTgts.emplace_back(PhotonTrackedTarget{
-centerRot.Z().convert<units::degrees>().to<double>(),
centerRot.Z().convert<units::degrees>().to<double>(),
-centerRot.Y().convert<units::degrees>().to<double>(), areaPercent,
centerRot.X().convert<units::degrees>().to<double>(), tgt.fiducialId,
pnpSim.best, pnpSim.alt, pnpSim.ambiguity, smallVec, cornersDouble});

View File

@@ -256,8 +256,7 @@ class VisionSystemSimTest {
cameraSim.setMinTargetAreaPixels(0.0);
visionSysSim.addVisionTargets(new VisionTargetSim(targetPose, new TargetModel(0.5, 0.5), 3));
// If the robot is rotated x deg (CCW+), the target yaw should be x deg (CW+)
var robotPose = new Pose2d(new Translation2d(10, 0), Rotation2d.fromDegrees(testYaw));
var robotPose = new Pose2d(new Translation2d(10, 0), Rotation2d.fromDegrees(-1.0 * testYaw));
visionSysSim.update(robotPose);
var res = camera.getLatestResult();
assertTrue(res.hasTargets());

View File

@@ -220,9 +220,8 @@ TEST_P(VisionSystemSimTestWithParamsTest, YawAngles) {
visionSysSim.AddVisionTargets({photon::VisionTargetSim{
targetPose, photon::TargetModel{0.5_m, 0.5_m}, 3}});
// If the robot is rotated x deg (CCW+), the target yaw should be x deg (CW+)
robotPose =
frc::Pose2d{frc::Translation2d{10_m, 0_m}, frc::Rotation2d{GetParam()}};
robotPose = frc::Pose2d{frc::Translation2d{10_m, 0_m},
frc::Rotation2d{-1 * GetParam()}};
visionSysSim.Update(robotPose);
ASSERT_TRUE(camera.GetLatestResult().HasTargets());
ASSERT_NEAR(GetParam().to<double>(),

View File

@@ -35,7 +35,6 @@ import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.LogLevel;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.logging.PvCSCoreLogger;
import org.photonvision.common.networking.NetworkManager;
import org.photonvision.common.util.TestUtils;
import org.photonvision.common.util.numbers.IntegerCouple;
@@ -66,7 +65,6 @@ public class Main {
private static final boolean isRelease = PhotonVersion.isRelease;
private static boolean isTestMode = false;
private static boolean isSmoketest = false;
private static Path testModeFolder = null;
private static boolean printDebugLogs;
@@ -92,11 +90,6 @@ public class Main {
"clear-config",
false,
"Clears PhotonVision pipeline and networking settings. Preserves log files");
options.addOption(
"s",
"smoketest",
false,
"Exit Photon after loading native libraries and camera configs, but before starting up camera runners");
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
@@ -134,10 +127,6 @@ public class Main {
if (cmd.hasOption("clear-config")) {
ConfigManager.getInstance().clearConfig();
}
if (cmd.hasOption("smoketest")) {
isSmoketest = true;
}
}
return true;
}
@@ -348,17 +337,11 @@ public class Main {
public static void main(String[] args) {
try {
boolean success = TestUtils.loadLibraries();
if (!success) {
logger.error("Failed to load native libraries! Giving up :(");
System.exit(1);
}
TestUtils.loadLibraries();
logger.info("Native libraries loaded.");
} catch (Exception e) {
logger.error("Failed to load native libraries!", e);
System.exit(1);
}
logger.info("Native libraries loaded.");
try {
if (Platform.isRaspberryPi()) {
@@ -410,8 +393,6 @@ public class Main {
+ Platform.getPlatformName()
+ (Platform.isRaspberryPi() ? (" (Pi " + PiVersion.getPiVersion() + ")") : ""));
PvCSCoreLogger.getInstance();
logger.debug("Loading ConfigManager...");
ConfigManager.getInstance().load(); // init config manager
ConfigManager.getInstance().requestSave();
@@ -431,11 +412,6 @@ public class Main {
NeuralNetworkModelManager.getInstance()
.initialize(ConfigManager.getInstance().getModelsDirectory());
if (isSmoketest) {
logger.info("PhotonVision base functionality loaded -- smoketest complete");
System.exit(0);
}
if (!isTestMode) {
logger.debug("Loading VisionSourceManager...");
VisionSourceManager.getInstance()

View File

@@ -1,7 +1,7 @@
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
id "com.dorongold.task-tree" version "2.1.0"
}

View File

@@ -1,7 +1,7 @@
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
id "com.dorongold.task-tree" version "2.1.0"
}

View File

@@ -39,8 +39,8 @@ class Robot : public frc::TimedRobot {
void TeleopPeriodic() override;
private:
// Change this to match the name of your camera as shown in the web UI
photon::PhotonCamera camera{"YOUR_CAMERA_NAME_HERE"};
// Change this to match the name of your camera
photon::PhotonCamera camera{"photonvision"};
// PID constants should be tuned per robot
frc::PIDController controller{.1, 0, 0};

View File

@@ -1,7 +1,7 @@
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
id "com.dorongold.task-tree" version "2.1.0"
}

View File

@@ -1,7 +1,7 @@
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
id "com.dorongold.task-tree" version "2.1.0"
}

View File

@@ -1,7 +1,7 @@
plugins {
id "cpp"
id "google-test-test-suite"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
id "com.dorongold.task-tree" version "2.1.0"
}
@@ -12,8 +12,8 @@ repositories {
}
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = "2024.3.1"
wpi.versions.wpimathVersion = "2024.3.1"
wpi.versions.wpilibVersion = "2024.2.1"
wpi.versions.wpimathVersion = "2024.2.1"
apply from: "${rootDir}/../shared/examples_common.gradle"

View File

@@ -1,6 +1,6 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
}
sourceCompatibility = JavaVersion.VERSION_11

View File

@@ -1,6 +1,6 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
}
sourceCompatibility = JavaVersion.VERSION_11

View File

@@ -48,8 +48,8 @@ public class Robot extends TimedRobot {
// How far from the target we want to be
final double GOAL_RANGE_METERS = Units.feetToMeters(3);
// Change this to match the name of your camera as shown in the web UI
PhotonCamera camera = new PhotonCamera("YOUR_CAMERA_NAME_HERE");
// Change this to match the name of your camera
PhotonCamera camera = new PhotonCamera("photonvision");
// PID constants should be tuned per robot
final double LINEAR_P = 0.1;

View File

@@ -1,6 +1,6 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
}
sourceCompatibility = JavaVersion.VERSION_11

View File

@@ -1,6 +1,6 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
}
sourceCompatibility = JavaVersion.VERSION_11

View File

@@ -1,6 +1,6 @@
plugins {
id "java"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id "edu.wpi.first.GradleRIO" version "2024.2.1"
}
sourceCompatibility = JavaVersion.VERSION_11
@@ -11,8 +11,8 @@ apply from: "${rootDir}/../shared/examples_common.gradle"
def ROBOT_MAIN_CLASS = "frc.robot.Main"
wpi.maven.useDevelopment = true
wpi.versions.wpilibVersion = "2024.3.1"
wpi.versions.wpimathVersion = "2024.3.1"
wpi.versions.wpilibVersion = "2024.2.1"
wpi.versions.wpimathVersion = "2024.2.1"
// Define my targets (RoboRIO) and artifacts (deployable files)

View File

@@ -80,9 +80,6 @@ if [[ "$DISTRO" = "Ubuntu" && "$INSTALL_NETWORK_MANAGER" != "true" && -z "$QUIET
fi
fi
echo "Update package list"
apt-get update
echo "Installing curl..."
apt-get install --yes curl
echo "curl installation complete."
@@ -139,9 +136,6 @@ echo "Installing v4l-utils..."
apt-get install --yes v4l-utils
echo "v4l-utils installation complete."
echo "Installing sqlite3"
apt-get install --yes sqlite3
echo "Downloading latest stable release of PhotonVision..."
mkdir -p /opt/photonvision
cd /opt/photonvision