Skip to content

Commit

Permalink
Merge branch 'PhotonVision:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
laviRZ authored Feb 24, 2024
2 parents 8a0edbb + ec66645 commit 7745876
Show file tree
Hide file tree
Showing 24 changed files with 485 additions and 59 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,13 @@ jobs:
- os: ubuntu-latest
artifact-name: LinuxArm64
image_suffix: orangepi5
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.6/photonvision_opi5.img.xz
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.9/photonvision_opi5.img.xz
cpu: cortex-a8
image_additional_mb: 4096
- os: ubuntu-latest
artifact-name: LinuxArm64
image_suffix: orangepi5plus
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.6/photonvision_opi5plus.img.xz
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.9/photonvision_opi5plus.img.xz
cpu: cortex-a8
image_additional_mb: 4096

Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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.2.1"
id "edu.wpi.first.GradleRIO" version "2024.3.1"
id 'edu.wpi.first.WpilibTools' version '1.3.0'
id 'com.google.protobuf' version '0.9.4' apply false
}
Expand All @@ -24,7 +24,7 @@ allprojects {
apply from: "versioningHelper.gradle"

ext {
wpilibVersion = "2024.2.1"
wpilibVersion = "2024.3.1"
wpimathVersion = wpilibVersion
openCVversion = "4.8.0-2"
joglVersion = "2.4.0-rc-20200307"
Expand Down
22 changes: 20 additions & 2 deletions photon-client/src/components/settings/NetworkingCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,29 @@ watchEffect(() => {
</v-banner>
<pv-switch
v-model="tempSettingsStruct.matchCamerasOnlyByPath"
label="Match cameras by-path ONLY"
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"
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ public class NetworkConfig {

/**
* If we should ONLY match cameras by path, and NEVER only by base-name. For now default to false
* to preserve old matching logic
* to preserve old matching logic.
*
* <p>This also disables creating new CameraConfigurations for detected "new" cameras.
*/
public boolean matchCamerasOnlyByPath = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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;
Expand Down Expand Up @@ -80,15 +81,27 @@ public Optional<String> getUSBPath() {
}

@Override
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;
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
Expand All @@ -101,6 +114,8 @@ public String toString() {
+ vendorId
+ ", pid="
+ productId
+ ", path="
+ path
+ ", otherPaths="
+ Arrays.toString(otherPaths)
+ "]";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* 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'");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ public USBCameraSource(CameraConfiguration config) {
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 (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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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 {
Expand Down Expand Up @@ -146,8 +147,8 @@ protected List<VisionSource> tryMatchCamImpl(ArrayList<CameraInfo> cameraInfos)
}

// Return no new sources because there are no new sources
if (connectedCameras.isEmpty() && !cameraInfos.isEmpty()) {
if (hasWarnedNoCameras) {
if (connectedCameras.isEmpty()) {
if (!hasWarnedNoCameras) {
logger.warn(
"No cameras were detected! Check that all cameras are connected, and that the path is correct.");
hasWarnedNoCameras = true;
Expand Down Expand Up @@ -186,7 +187,7 @@ protected List<VisionSource> tryMatchCamImpl(ArrayList<CameraInfo> cameraInfos)
"Unloaded configs: "
+ unmatchedLoadedConfigs.stream()
.map(it -> it.nickname)
.collect(Collectors.joining()));
.collect(Collectors.joining(", ")));
hasWarned = true;
}

Expand All @@ -195,13 +196,8 @@ protected List<VisionSource> tryMatchCamImpl(ArrayList<CameraInfo> cameraInfos)

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);
var sources = loadVisionSourcesFromCamConfigs(matchedCameras, createSources);

// Print info about each vision source
for (var src : sources) {
Expand Down Expand Up @@ -317,40 +313,62 @@ public List<CameraConfiguration> matchCameras(
logger.info("Matching by usb port & name & USB VID/PID...");
cameraConfigurations.addAll(
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, true, true, true, false));
} else
logger.debug("Skipping match by usb port/name/vid/pid, 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()) {
if (Platform.isWindows() && !matchCamerasOnlyByPath) {
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
logger.info("Matching by windows-path & USB VID/PID only...");
cameraConfigurations.addAll(
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, false, true, true, true));
} else
logger.debug(
"Skipping matching by windiws-path/name/vid/pid, no configs or cameras left to match");
}
}

if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
logger.info("Matching by usb port & USB VID/PID...");
cameraConfigurations.addAll(
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, true, true, false, false));
} else logger.debug("Skipping match by port/vid/pid, no configs or cameras left to match");
}

// 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
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
logger.info("Matching by base-name & usb port...");
cameraConfigurations.addAll(
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, true, false, true, false));
}

// handle disabling only-by-base-name matching
if (!matchCamerasOnlyByPath) {
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
logger.info("Matching by base-name & USB VID/PID only...");
cameraConfigurations.addAll(
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, false, true, true, false));
} else
logger.debug("Skipping match by base-name/viid/pid, no configs or cameras left to match");
}

// Legacy migration for if no USB VID/PID set
if (detectedCameraList.size() > 0 || unloadedConfigs.size() > 0) {
logger.info("Matching by base-name only...");
cameraConfigurations.addAll(
matchCamerasByStrategy(detectedCameraList, unloadedConfigs, false, false, true, false));
}
} else logger.info("Skipping match by filepath/vid/pid, disabled by user");

if (detectedCameraList.size() > 0) {
cameraConfigurations.addAll(
createConfigsForCameras(detectedCameraList, unloadedConfigs, cameraConfigurations));
// 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");
}
}

logger.debug("Matched or created " + cameraConfigurations.size() + " camera configs!");
Expand Down Expand Up @@ -434,7 +452,8 @@ private List<CameraConfiguration> createConfigsForCameras(
int suffix = 0;
while (containsName(loadedConfigs, uniqueName)
|| containsName(uniqueName)
|| containsName(unloadedCamConfigs, uniqueName)) {
|| containsName(unloadedCamConfigs, uniqueName)
|| containsName(ret, uniqueName)) {
suffix++;
uniqueName = String.format("%s (%d)", uniqueName, suffix);
}
Expand Down Expand Up @@ -514,11 +533,17 @@ private List<CameraInfo> filterAllowedDevices(List<CameraInfo> allDevices) {
}

private static List<VisionSource> loadVisionSourcesFromCamConfigs(
List<CameraConfiguration> camConfigs) {
List<CameraConfiguration> camConfigs, boolean createSources) {
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;
}

boolean is_pi = Platform.isRaspberryPi();

if (configuration.cameraType == CameraType.ZeroCopyPicam && is_pi) {
Expand Down
Loading

0 comments on commit 7745876

Please sign in to comment.