mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-02 02:51:40 +00:00
Config (#101)
* Add some config stuff, run format * Create JacksonUtils.java * Fix deserialization, run wpiformat * Update ConfigTest.java * Change config root folder, auto-sort pipelines when added to CameraConfiguration * Split ConfigTest tests in to multiple tests * Fix unsatisfied link error in config test * Add equals and hashCode to all PipelineSettings classes * Fix NPE in config test * run spotless * Address changes Co-authored-by: Banks Troutman <btrout.dhrs@gmail.com> Co-authored-by: ori agranat <oriagranat9@gmail.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -122,4 +122,4 @@ New client/chameleon-client/*
|
||||
.DS_Store
|
||||
# *.iml
|
||||
chameleon-server/build
|
||||
chameleon-server/chameleon-vision (1).iml
|
||||
chameleon-server/chameleon-vision
|
||||
|
||||
3
chameleon-server/.gitignore
vendored
3
chameleon-server/.gitignore
vendored
@@ -2,4 +2,5 @@ bin/*
|
||||
.settings/*
|
||||
.project
|
||||
.classpath
|
||||
*.prefs
|
||||
*.prefs
|
||||
chameleonVision
|
||||
@@ -41,8 +41,6 @@ dependencies {
|
||||
implementation "org.apache.commons:commons-collections4:4.4"
|
||||
implementation "org.apache.commons:commons-exec:1.3"
|
||||
|
||||
implementation "com.moandjiezana.toml:toml4j:0.7.2"
|
||||
|
||||
// wpilib stuff
|
||||
implementation "edu.wpi.first.wpiutil:wpiutil-java:$wpilibVersion"
|
||||
implementation "edu.wpi.first.cameraserver:cameraserver-java:$wpilibVersion"
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.chameleonvision._2.config;
|
||||
|
||||
import com.chameleonvision._2.vision.pipeline.CVPipelineSettings;
|
||||
import com.chameleonvision.common.util.file.FileUtils;
|
||||
import com.chameleonvision.common.util.file.JacksonUtils;
|
||||
import com.chameleonvision.common.util.jackson.JacksonUtils;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.chameleonvision._2.vision.pipeline.CVPipelineSettings;
|
||||
import com.chameleonvision.common.util.Platform;
|
||||
import com.chameleonvision.common.util.ShellExec;
|
||||
import com.chameleonvision.common.util.file.FileUtils;
|
||||
import com.chameleonvision.common.util.file.JacksonUtils;
|
||||
import com.chameleonvision.common.util.jackson.JacksonUtils;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.chameleonvision._2.config.serializers.StandardCVPipelineSettingsSeria
|
||||
import com.chameleonvision._2.vision.pipeline.CVPipelineSettings;
|
||||
import com.chameleonvision._2.vision.pipeline.impl.StandardCVPipelineSettings;
|
||||
import com.chameleonvision.common.util.file.FileUtils;
|
||||
import com.chameleonvision.common.util.file.JacksonUtils;
|
||||
import com.chameleonvision.common.util.jackson.JacksonUtils;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.chameleonvision.common.configuration;
|
||||
|
||||
import com.chameleonvision.common.calibration.CameraCalibrationCoefficients;
|
||||
import com.chameleonvision.common.logging.LogGroup;
|
||||
import com.chameleonvision.common.logging.Logger;
|
||||
import com.chameleonvision.common.vision.pipeline.CVPipelineSettings;
|
||||
import com.chameleonvision.common.vision.pipeline.DriverModePipelineSettings;
|
||||
import com.chameleonvision.common.vision.processes.PipelineManager;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CameraConfiguration {
|
||||
private static final Logger logger = new Logger(CameraConfiguration.class, LogGroup.Camera);
|
||||
|
||||
public String name = "";
|
||||
public String nickname = "";
|
||||
public double FOV = 70;
|
||||
public CameraCalibrationCoefficients calibration;
|
||||
|
||||
@JsonIgnore // this ignores the pipes as we serialize them to their own subfolder
|
||||
public final List<CVPipelineSettings> pipelineSettings = new ArrayList<>();
|
||||
|
||||
@JsonIgnore
|
||||
public DriverModePipelineSettings driveModeSettings = new DriverModePipelineSettings();
|
||||
|
||||
public void addPipelineSettings(List<CVPipelineSettings> settings) {
|
||||
for (var setting : settings) {
|
||||
addPipelineSetting(setting);
|
||||
}
|
||||
}
|
||||
|
||||
public void addPipelineSetting(CVPipelineSettings setting) {
|
||||
if (pipelineSettings.stream()
|
||||
.anyMatch(s -> s.pipelineNickname.equalsIgnoreCase(setting.pipelineNickname))) {
|
||||
logger.error("Could not name two pipelines the same thing! Renaming");
|
||||
setting.pipelineNickname += "_1"; // TODO verify this logic
|
||||
}
|
||||
|
||||
if (pipelineSettings.stream().anyMatch(s -> s.pipelineIndex == setting.pipelineIndex)) {
|
||||
var newIndex = pipelineSettings.size();
|
||||
logger.error("Could not insert two pipelines at same index! Changing to " + newIndex);
|
||||
setting.pipelineIndex = newIndex; // TODO verify this logic
|
||||
}
|
||||
|
||||
pipelineSettings.add(setting);
|
||||
pipelineSettings.sort(PipelineManager.PipelineSettingsIndexComparator);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.chameleonvision.common.configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ChameleonConfiguration {
|
||||
public HardwareConfig getHardwareConfig() {
|
||||
return hardwareConfig;
|
||||
}
|
||||
|
||||
public NetworkConfig getNetworkConfig() {
|
||||
return networkConfig;
|
||||
}
|
||||
|
||||
public HashMap<String, CameraConfiguration> getCameraConfigurations() {
|
||||
return cameraConfigurations;
|
||||
}
|
||||
|
||||
public void addCameraConfig(CameraConfiguration config) {
|
||||
addCameraConfig(config.name, config);
|
||||
}
|
||||
|
||||
public void addCameraConfig(String name, CameraConfiguration config) {
|
||||
cameraConfigurations.put(name, config);
|
||||
}
|
||||
|
||||
private HardwareConfig hardwareConfig;
|
||||
private NetworkConfig networkConfig;
|
||||
|
||||
private HashMap<String, CameraConfiguration> cameraConfigurations;
|
||||
|
||||
public ChameleonConfiguration(HardwareConfig hardwareConfig, NetworkConfig networkConfig) {
|
||||
this(hardwareConfig, networkConfig, new HashMap<>());
|
||||
}
|
||||
|
||||
public ChameleonConfiguration(
|
||||
HardwareConfig hardwareConfig,
|
||||
NetworkConfig networkConfig,
|
||||
HashMap<String, CameraConfiguration> cameraConfigurations) {
|
||||
this.hardwareConfig = hardwareConfig;
|
||||
this.networkConfig = networkConfig;
|
||||
this.cameraConfigurations = cameraConfigurations;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.chameleonvision.common.configuration;
|
||||
|
||||
public class ConfigFile {
|
||||
|
||||
/**
|
||||
* Represents a config file at a fixed path
|
||||
*
|
||||
* @param path Path to config file
|
||||
*/
|
||||
public ConfigFile(String path) {}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.chameleonvision.common.configuration;
|
||||
|
||||
public class ConfigFolder {
|
||||
|
||||
/**
|
||||
* Represents a folder of config files
|
||||
*
|
||||
* @param path path to config file
|
||||
*/
|
||||
public ConfigFolder(String path) {}
|
||||
}
|
||||
@@ -1,18 +1,229 @@
|
||||
package com.chameleonvision.common.configuration;
|
||||
|
||||
import com.chameleonvision.common.logging.LogGroup;
|
||||
import com.chameleonvision.common.logging.Logger;
|
||||
import com.chameleonvision.common.util.file.JacksonUtils;
|
||||
import com.chameleonvision.common.vision.pipeline.CVPipelineSettings;
|
||||
import com.chameleonvision.common.vision.pipeline.DriverModePipelineSettings;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ConfigManager {
|
||||
private static final Logger logger = new Logger(ConfigManager.class, LogGroup.General);
|
||||
private static ConfigManager INSTANCE;
|
||||
|
||||
private final ConfigFolder rootFolder;
|
||||
|
||||
protected ConfigManager() {
|
||||
rootFolder = new ConfigFolder("");
|
||||
}
|
||||
|
||||
private static class SingletonHolder {
|
||||
private static final ConfigManager INSTANCE = new ConfigManager();
|
||||
}
|
||||
private ChameleonConfiguration config;
|
||||
private final File rootFolder;
|
||||
private final File hardwareConfigFile;
|
||||
private final File networkConfigFile;
|
||||
private final File camerasFolder;
|
||||
|
||||
public static ConfigManager getInstance() {
|
||||
return SingletonHolder.INSTANCE;
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new ConfigManager(getRootFolder());
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public ChameleonConfiguration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
protected static Path getRootFolder() {
|
||||
return Path.of("chameleon-vision"); // TODO change root folder?
|
||||
}
|
||||
|
||||
private ConfigManager(Path rootFolder) {
|
||||
this.rootFolder = new File(rootFolder.toUri());
|
||||
this.hardwareConfigFile =
|
||||
new File(Path.of(rootFolder.toString(), "hardwareConfig.json").toUri());
|
||||
this.networkConfigFile =
|
||||
new File(Path.of(rootFolder.toString(), "networkSettings.json").toUri());
|
||||
this.camerasFolder = new File(Path.of(rootFolder.toString(), "cameras").toUri());
|
||||
load();
|
||||
}
|
||||
|
||||
public void load() {
|
||||
logger.info("Loading settings...");
|
||||
if (!rootFolder.exists()) {
|
||||
if (rootFolder.mkdirs()) {
|
||||
logger.debug("Root config folder did not exist. Created!");
|
||||
} else {
|
||||
logger.error("Failed to create root config folder!");
|
||||
}
|
||||
}
|
||||
|
||||
HardwareConfig hardwareConfig;
|
||||
NetworkConfig networkConfig;
|
||||
|
||||
try {
|
||||
hardwareConfig = JacksonUtils.deserialize(hardwareConfigFile.toPath(), HardwareConfig.class);
|
||||
if (hardwareConfig == null) {
|
||||
logger.error("Could not deserialize hardware config! Loading defaults");
|
||||
hardwareConfig = new HardwareConfig();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not deserialize hardware config! Loading defaults");
|
||||
hardwareConfig = new HardwareConfig();
|
||||
}
|
||||
|
||||
try {
|
||||
networkConfig = JacksonUtils.deserialize(networkConfigFile.toPath(), NetworkConfig.class);
|
||||
if (networkConfig == null) {
|
||||
logger.error("Could not deserialize network config! Loading defaults");
|
||||
networkConfig = new NetworkConfig();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not deserialize network config! Loading defaults");
|
||||
networkConfig = new NetworkConfig();
|
||||
}
|
||||
|
||||
if (!camerasFolder.exists()) {
|
||||
if (camerasFolder.mkdirs()) {
|
||||
logger.debug("Cameras config folder did not exist. Created!");
|
||||
} else {
|
||||
logger.error("Failed to create cameras config folder!");
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<String, CameraConfiguration> cameraConfigurations = loadCameraConfigs();
|
||||
|
||||
this.config = new ChameleonConfiguration(hardwareConfig, networkConfig, cameraConfigurations);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
logger.info("Saving settings...");
|
||||
|
||||
try {
|
||||
JacksonUtils.serializer(hardwareConfigFile.toPath(), config.getHardwareConfig());
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not save hardware config!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
JacksonUtils.serializer(networkConfigFile.toPath(), config.getNetworkConfig());
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not save network config!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// save all of our cameras
|
||||
var cameraConfigMap = config.getCameraConfigurations();
|
||||
for (var subdirName : cameraConfigMap.keySet()) {
|
||||
var camConfig = cameraConfigMap.get(subdirName);
|
||||
var subdir = Path.of(camerasFolder.toPath().toString(), subdirName);
|
||||
|
||||
if (!subdir.toFile().exists()) {
|
||||
subdir.toFile().mkdirs();
|
||||
}
|
||||
|
||||
try {
|
||||
JacksonUtils.serializer(Path.of(subdir.toString(), "config.json"), camConfig);
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not save config.json for " + subdir);
|
||||
}
|
||||
|
||||
try {
|
||||
JacksonUtils.serializer(
|
||||
Path.of(subdir.toString(), "drivermode.json"), camConfig.driveModeSettings);
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not save drivermode.json for " + subdir);
|
||||
}
|
||||
|
||||
for (var pipe : camConfig.pipelineSettings) {
|
||||
var pipePath = Path.of(subdir.toString(), "pipelines", pipe.pipelineNickname + ".json");
|
||||
|
||||
if (!pipePath.getParent().toFile().exists()) {
|
||||
pipePath.getParent().toFile().mkdirs();
|
||||
}
|
||||
|
||||
try {
|
||||
JacksonUtils.serializer(pipePath, pipe);
|
||||
} catch (IOException e) {
|
||||
logger.error("Could not save " + pipe.pipelineNickname + ".json!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HashMap<String, CameraConfiguration> loadCameraConfigs() {
|
||||
HashMap<String, CameraConfiguration> loadedConfigurations = new HashMap<>();
|
||||
try {
|
||||
var subdirectories =
|
||||
Files.list(camerasFolder.toPath())
|
||||
.filter(f -> f.toFile().isDirectory())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (var subdir : subdirectories) {
|
||||
var cameraConfigPath = Path.of(subdir.toString(), "config.json");
|
||||
CameraConfiguration loadedConfig =
|
||||
JacksonUtils.deserialize(cameraConfigPath.toAbsolutePath(), CameraConfiguration.class);
|
||||
if (loadedConfig == null) {
|
||||
logger.warn("Could not load camera " + subdir + "'s config.json! Loading " + "default");
|
||||
loadedConfig = new CameraConfiguration();
|
||||
}
|
||||
|
||||
// At this point we have only loaded the base stuff
|
||||
// We still need to deserialize pipelines, as well as
|
||||
// driver mode settings
|
||||
var driverModeFile = Path.of(subdir.toString(), "drivermode.json");
|
||||
DriverModePipelineSettings driverMode;
|
||||
try {
|
||||
driverMode =
|
||||
JacksonUtils.deserialize(
|
||||
driverModeFile.toAbsolutePath(), DriverModePipelineSettings.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Could not deserialize drivermode.json! Loading defaults");
|
||||
logger.de_pest(Arrays.toString(e.getStackTrace()));
|
||||
driverMode = new DriverModePipelineSettings();
|
||||
}
|
||||
if (driverMode == null) {
|
||||
logger.warn(
|
||||
"Could not load camera " + subdir + "'s drivermode.json! Loading" + " default");
|
||||
driverMode = new DriverModePipelineSettings();
|
||||
}
|
||||
|
||||
// Load pipelines by mapping the files within the pipelines subdir
|
||||
// to their deserialized equivalents
|
||||
List<CVPipelineSettings> settings =
|
||||
Files.list(Path.of(subdir.toString(), "pipelines"))
|
||||
.filter(p -> p.toFile().isFile())
|
||||
.map(
|
||||
p -> {
|
||||
var relativizedFilePath =
|
||||
getRootFolder().toAbsolutePath().relativize(p).toString();
|
||||
try {
|
||||
var ret = JacksonUtils.deserialize(p, CVPipelineSettings.class);
|
||||
return ret;
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Exception while deserializing " + relativizedFilePath);
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
logger.warn(
|
||||
"Could not load pipeline at " + relativizedFilePath + "! Skipping...");
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
loadedConfig.driveModeSettings = driverMode;
|
||||
loadedConfig.addPipelineSettings(settings);
|
||||
|
||||
loadedConfigurations.put(subdir.toFile().getName(), loadedConfig);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return loadedConfigurations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.chameleonvision.common.configuration;
|
||||
|
||||
/** Defines a;lskdjfa;dsklf */
|
||||
public class HardwareConfig {
|
||||
public int ledPin = 1; // just to stop jackson from yeeting
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.chameleonvision.common.configuration;
|
||||
|
||||
import com.chameleonvision.common.networking.NetworkMode;
|
||||
|
||||
public class NetworkConfig {
|
||||
public int teamNumber = 1577;
|
||||
public NetworkMode connectionType = NetworkMode.DHCP;
|
||||
public String ip = "";
|
||||
public String gateway = "";
|
||||
public String netmask = "";
|
||||
public String hostname = "chameleon-vision";
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.chameleonvision.common.util.numbers;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public abstract class NumberCouple<T extends Number> {
|
||||
|
||||
protected T first;
|
||||
@@ -49,6 +51,7 @@ public abstract class NumberCouple<T extends Number> {
|
||||
return true;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isEmpty() {
|
||||
return first.intValue() == 0 && second.intValue() == 0;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.chameleonvision.common.vision.opencv.ContourSortMode;
|
||||
import com.chameleonvision.common.vision.target.RobotOffsetPointMode;
|
||||
import com.chameleonvision.common.vision.target.TargetOffsetPointEdge;
|
||||
import com.chameleonvision.common.vision.target.TargetOrientation;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AdvancedPipelineSettings extends CVPipelineSettings {
|
||||
|
||||
@@ -49,4 +50,54 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
|
||||
// the two values that define the line of the Dual Point Offset calibration (think y=mx+b)
|
||||
public double offsetDualLineM = 1;
|
||||
public double offsetDualLineB = 0;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
AdvancedPipelineSettings that = (AdvancedPipelineSettings) o;
|
||||
return outputShowThresholded == that.outputShowThresholded
|
||||
&& outputShowMultipleTargets == that.outputShowMultipleTargets
|
||||
&& erode == that.erode
|
||||
&& dilate == that.dilate
|
||||
&& contourSpecklePercentage == that.contourSpecklePercentage
|
||||
&& Double.compare(that.offsetDualLineM, offsetDualLineM) == 0
|
||||
&& Double.compare(that.offsetDualLineB, offsetDualLineB) == 0
|
||||
&& hsvHue.equals(that.hsvHue)
|
||||
&& hsvSaturation.equals(that.hsvSaturation)
|
||||
&& hsvValue.equals(that.hsvValue)
|
||||
&& contourArea.equals(that.contourArea)
|
||||
&& contourRatio.equals(that.contourRatio)
|
||||
&& contourExtent.equals(that.contourExtent)
|
||||
&& contourSortMode == that.contourSortMode
|
||||
&& contourTargetOffsetPointEdge == that.contourTargetOffsetPointEdge
|
||||
&& contourTargetOrientation == that.contourTargetOrientation
|
||||
&& offsetRobotOffsetMode == that.offsetRobotOffsetMode
|
||||
&& offsetCalibrationPoint.equals(that.offsetCalibrationPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
super.hashCode(),
|
||||
hsvHue,
|
||||
hsvSaturation,
|
||||
hsvValue,
|
||||
outputShowThresholded,
|
||||
outputShowMultipleTargets,
|
||||
erode,
|
||||
dilate,
|
||||
contourArea,
|
||||
contourRatio,
|
||||
contourExtent,
|
||||
contourSpecklePercentage,
|
||||
contourSortMode,
|
||||
contourTargetOffsetPointEdge,
|
||||
contourTargetOrientation,
|
||||
offsetRobotOffsetMode,
|
||||
offsetCalibrationPoint,
|
||||
offsetDualLineM,
|
||||
offsetDualLineB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,19 @@ package com.chameleonvision.common.vision.pipeline;
|
||||
import com.chameleonvision.common.vision.frame.FrameDivisor;
|
||||
import com.chameleonvision.common.vision.pipe.ImageFlipMode;
|
||||
import com.chameleonvision.common.vision.pipe.ImageRotationMode;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import java.util.Objects;
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.WRAPPER_ARRAY,
|
||||
property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = ColoredShapePipelineSettings.class),
|
||||
@JsonSubTypes.Type(value = ReflectivePipelineSettings.class),
|
||||
@JsonSubTypes.Type(value = DriverModePipelineSettings.class)
|
||||
})
|
||||
public class CVPipelineSettings {
|
||||
public int pipelineIndex = 0;
|
||||
public PipelineType pipelineType = PipelineType.DriverMode;
|
||||
@@ -17,4 +29,40 @@ public class CVPipelineSettings {
|
||||
public FrameDivisor inputFrameDivisor = FrameDivisor.NONE;
|
||||
public FrameDivisor outputFrameDivisor = FrameDivisor.NONE;
|
||||
public boolean ledMode = false;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CVPipelineSettings that = (CVPipelineSettings) o;
|
||||
return pipelineIndex == that.pipelineIndex
|
||||
&& Double.compare(that.cameraExposure, cameraExposure) == 0
|
||||
&& Double.compare(that.cameraBrightness, cameraBrightness) == 0
|
||||
&& Double.compare(that.cameraGain, cameraGain) == 0
|
||||
&& cameraVideoModeIndex == that.cameraVideoModeIndex
|
||||
&& ledMode == that.ledMode
|
||||
&& pipelineType == that.pipelineType
|
||||
&& inputImageFlipMode == that.inputImageFlipMode
|
||||
&& inputImageRotationMode == that.inputImageRotationMode
|
||||
&& pipelineNickname.equals(that.pipelineNickname)
|
||||
&& inputFrameDivisor == that.inputFrameDivisor
|
||||
&& outputFrameDivisor == that.outputFrameDivisor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
pipelineIndex,
|
||||
pipelineType,
|
||||
inputImageFlipMode,
|
||||
inputImageRotationMode,
|
||||
pipelineNickname,
|
||||
cameraExposure,
|
||||
cameraBrightness,
|
||||
cameraGain,
|
||||
cameraVideoModeIndex,
|
||||
inputFrameDivisor,
|
||||
outputFrameDivisor,
|
||||
ledMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
package com.chameleonvision.common.vision.pipeline;
|
||||
|
||||
import com.chameleonvision.common.vision.opencv.ContourShape;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import java.util.Objects;
|
||||
|
||||
@JsonTypeName("ColoredShapePipelineSettings")
|
||||
public class ColoredShapePipelineSettings extends AdvancedPipelineSettings {
|
||||
ContourShape desiredShape;
|
||||
|
||||
public ColoredShapePipelineSettings() {
|
||||
super();
|
||||
pipelineType = PipelineType.ColoredShape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
ColoredShapePipelineSettings that = (ColoredShapePipelineSettings) o;
|
||||
return desiredShape == that.desiredShape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), desiredShape);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,15 @@ package com.chameleonvision.common.vision.pipeline;
|
||||
|
||||
import com.chameleonvision.common.util.numbers.DoubleCouple;
|
||||
import com.chameleonvision.common.vision.target.RobotOffsetPointMode;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
|
||||
@JsonTypeName("DriverModePipelineSettings")
|
||||
public class DriverModePipelineSettings extends CVPipelineSettings {
|
||||
public RobotOffsetPointMode offsetPointMode = RobotOffsetPointMode.None;
|
||||
public DoubleCouple offsetPoint = new DoubleCouple();
|
||||
|
||||
public DriverModePipelineSettings() {
|
||||
super();
|
||||
pipelineType = PipelineType.DriverMode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ import com.chameleonvision.common.vision.opencv.ContourGroupingMode;
|
||||
import com.chameleonvision.common.vision.opencv.ContourIntersectionDirection;
|
||||
import com.chameleonvision.common.vision.pipe.impl.CornerDetectionPipe;
|
||||
import com.chameleonvision.common.vision.target.TargetModel;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||
import edu.wpi.first.wpilibj.geometry.Rotation2d;
|
||||
import java.util.Objects;
|
||||
|
||||
@JsonTypeName("ReflectivePipelineSettings")
|
||||
public class ReflectivePipelineSettings extends AdvancedPipelineSettings {
|
||||
// how many contours to attempt to group (Single, Dual)
|
||||
public ContourGroupingMode contourGroupingMode = ContourGroupingMode.Single;
|
||||
@@ -27,4 +30,46 @@ public class ReflectivePipelineSettings extends AdvancedPipelineSettings {
|
||||
public boolean cornerDetectionExactSideCount = false;
|
||||
public int cornerDetectionSideCount = 4;
|
||||
public double cornerDetectionAccuracyPercentage = 10;
|
||||
|
||||
public ReflectivePipelineSettings() {
|
||||
super();
|
||||
pipelineType = PipelineType.Reflective;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
ReflectivePipelineSettings that = (ReflectivePipelineSettings) o;
|
||||
return solvePNPEnabled == that.solvePNPEnabled
|
||||
&& cornerDetectionUseConvexHulls == that.cornerDetectionUseConvexHulls
|
||||
&& cornerDetectionExactSideCount == that.cornerDetectionExactSideCount
|
||||
&& cornerDetectionSideCount == that.cornerDetectionSideCount
|
||||
&& Double.compare(that.cornerDetectionAccuracyPercentage, cornerDetectionAccuracyPercentage)
|
||||
== 0
|
||||
&& contourGroupingMode == that.contourGroupingMode
|
||||
&& contourIntersection == that.contourIntersection
|
||||
&& Objects.equals(cameraCalibration, that.cameraCalibration)
|
||||
&& targetModel.equals(that.targetModel)
|
||||
&& cameraPitch.equals(that.cameraPitch)
|
||||
&& cornerDetectionStrategy == that.cornerDetectionStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
super.hashCode(),
|
||||
contourGroupingMode,
|
||||
contourIntersection,
|
||||
solvePNPEnabled,
|
||||
cameraCalibration,
|
||||
targetModel,
|
||||
cameraPitch,
|
||||
cornerDetectionStrategy,
|
||||
cornerDetectionUseConvexHulls,
|
||||
cornerDetectionExactSideCount,
|
||||
cornerDetectionSideCount,
|
||||
cornerDetectionAccuracyPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,10 +115,10 @@ public class PipelineManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Comparator<CVPipeline> IndexComparator =
|
||||
public static final Comparator<CVPipelineSettings> PipelineSettingsIndexComparator =
|
||||
(o1, o2) -> {
|
||||
int o1Index = o1.getSettings().pipelineIndex;
|
||||
int o2Index = o2.getSettings().pipelineIndex;
|
||||
int o1Index = o1.pipelineIndex;
|
||||
int o2Index = o2.pipelineIndex;
|
||||
|
||||
if (o1Index == o2Index) {
|
||||
return 0;
|
||||
@@ -128,13 +128,16 @@ public class PipelineManager {
|
||||
return 1;
|
||||
};
|
||||
|
||||
public static final Comparator<CVPipeline> PipelineIndexComparator =
|
||||
(o1, o2) -> PipelineSettingsIndexComparator.compare(o1.getSettings(), o2.getSettings());
|
||||
|
||||
/**
|
||||
* Sorts the pipeline list by index, and reassigns their indexes to match the new order. <br>
|
||||
* <br>
|
||||
* I don't like this but I have no other ideas, and it works so ¯\_(ツ)_/¯
|
||||
*/
|
||||
private void reassignIndexes() {
|
||||
userPipelines.sort(IndexComparator);
|
||||
userPipelines.sort(PipelineIndexComparator);
|
||||
for (int i = 0; i < userPipelines.size(); i++) {
|
||||
getPipelineSettings(i).pipelineIndex = i;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
package com.chameleonvision.common.vision.target;
|
||||
|
||||
import com.chameleonvision.common.vision.opencv.Releasable;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.opencv.core.MatOfPoint3f;
|
||||
import org.opencv.core.Point3;
|
||||
|
||||
public class TargetModel implements Releasable {
|
||||
|
||||
private final MatOfPoint3f realWorldTargetCoordinates;
|
||||
@JsonIgnore private final MatOfPoint3f realWorldTargetCoordinates;
|
||||
@JsonIgnore private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f();
|
||||
@JsonIgnore private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f();
|
||||
|
||||
private final MatOfPoint3f visualizationBoxBottom = new MatOfPoint3f();
|
||||
private final MatOfPoint3f visualizationBoxTop = new MatOfPoint3f();
|
||||
public final List<Point3> realWorldCoordinatesArray;
|
||||
public final double boxHeight;
|
||||
|
||||
public TargetModel(MatOfPoint3f realWorldTargetCoordinates, double boxHeight) {
|
||||
this.realWorldTargetCoordinates = realWorldTargetCoordinates;
|
||||
this.realWorldCoordinatesArray = realWorldTargetCoordinates.toList();
|
||||
this.boxHeight = boxHeight;
|
||||
|
||||
var bottomList = realWorldTargetCoordinates.toList();
|
||||
var topList = new ArrayList<Point3>();
|
||||
@@ -26,7 +34,10 @@ public class TargetModel implements Releasable {
|
||||
this.visualizationBoxTop.fromList(topList);
|
||||
}
|
||||
|
||||
public TargetModel(List<Point3> points, double boxHeight) {
|
||||
@JsonCreator
|
||||
public TargetModel(
|
||||
@JsonProperty(value = "realWorldCoordinatesArray") List<Point3> points,
|
||||
@JsonProperty(value = "boxHeight") double boxHeight) {
|
||||
this(listToMat(points), boxHeight);
|
||||
}
|
||||
|
||||
@@ -76,6 +87,20 @@ public class TargetModel implements Releasable {
|
||||
return new TargetModel(corners, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof TargetModel)) return false;
|
||||
TargetModel that = (TargetModel) o;
|
||||
return Double.compare(that.boxHeight, boxHeight) == 0
|
||||
&& Objects.equals(realWorldCoordinatesArray, that.realWorldCoordinatesArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(realWorldCoordinatesArray, boxHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
realWorldTargetCoordinates.release();
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.chameleonvision.common.configuration;
|
||||
|
||||
import com.chameleonvision.common.logging.Level;
|
||||
import com.chameleonvision.common.logging.LogGroup;
|
||||
import com.chameleonvision.common.logging.Logger;
|
||||
import com.chameleonvision.common.util.TestUtils;
|
||||
import com.chameleonvision.common.util.file.JacksonUtils;
|
||||
import com.chameleonvision.common.vision.pipeline.ColoredShapePipelineSettings;
|
||||
import com.chameleonvision.common.vision.pipeline.ReflectivePipelineSettings;
|
||||
import com.chameleonvision.common.vision.target.TargetModel;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import org.junit.jupiter.api.*;
|
||||
|
||||
public class ConfigTest {
|
||||
|
||||
private static final ConfigManager configMgr;
|
||||
private static final CameraConfiguration cameraConfig = new CameraConfiguration();
|
||||
private static final ReflectivePipelineSettings REFLECTIVE_PIPELINE_SETTINGS =
|
||||
new ReflectivePipelineSettings();
|
||||
private static final ColoredShapePipelineSettings COLORED_SHAPE_PIPELINE_SETTINGS =
|
||||
new ColoredShapePipelineSettings();
|
||||
|
||||
static {
|
||||
TestUtils.loadLibraries();
|
||||
configMgr = ConfigManager.getInstance();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
public static void init() {
|
||||
TestUtils.loadLibraries();
|
||||
Logger.setLevel(LogGroup.General, Level.DE_PEST);
|
||||
|
||||
cameraConfig.name = "TestCamera";
|
||||
|
||||
REFLECTIVE_PIPELINE_SETTINGS.pipelineNickname = "2019Tape";
|
||||
REFLECTIVE_PIPELINE_SETTINGS.targetModel = TargetModel.get2019Target();
|
||||
|
||||
COLORED_SHAPE_PIPELINE_SETTINGS.pipelineNickname = "2019Cargo";
|
||||
COLORED_SHAPE_PIPELINE_SETTINGS.pipelineIndex = 1;
|
||||
|
||||
cameraConfig.addPipelineSetting(REFLECTIVE_PIPELINE_SETTINGS);
|
||||
cameraConfig.addPipelineSetting(COLORED_SHAPE_PIPELINE_SETTINGS);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
public void serializeConfig() throws IOException {
|
||||
TestUtils.loadLibraries();
|
||||
JacksonUtils.serializer(Path.of("settings.json"), REFLECTIVE_PIPELINE_SETTINGS);
|
||||
|
||||
Logger.setLevel(LogGroup.General, Level.DE_PEST);
|
||||
configMgr.getConfig().addCameraConfig(cameraConfig);
|
||||
configMgr.save();
|
||||
|
||||
var camConfDir =
|
||||
new File(
|
||||
Path.of(ConfigManager.getRootFolder().toString(), "cameras", "TestCamera")
|
||||
.toAbsolutePath()
|
||||
.toString());
|
||||
Assertions.assertTrue(camConfDir.exists(), "TestCamera config folder not found!");
|
||||
|
||||
Assertions.assertTrue(
|
||||
Files.exists(Path.of(ConfigManager.getRootFolder().toString(), "hardwareConfig.json")),
|
||||
"hardwareConfig.json file not found!");
|
||||
Assertions.assertTrue(
|
||||
Files.exists(Path.of(ConfigManager.getRootFolder().toString(), "networkSettings.json")),
|
||||
"networkSettings.json file not found!");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
public void deserializeConfig() {
|
||||
configMgr.load();
|
||||
|
||||
var reflectivePipelineSettings =
|
||||
configMgr.getConfig().getCameraConfigurations().get("TestCamera").pipelineSettings.get(0);
|
||||
var coloredShapePipelineSettings =
|
||||
configMgr.getConfig().getCameraConfigurations().get("TestCamera").pipelineSettings.get(1);
|
||||
|
||||
Assertions.assertEquals(REFLECTIVE_PIPELINE_SETTINGS, reflectivePipelineSettings);
|
||||
Assertions.assertEquals(COLORED_SHAPE_PIPELINE_SETTINGS, coloredShapePipelineSettings);
|
||||
|
||||
Assertions.assertTrue(
|
||||
reflectivePipelineSettings instanceof ReflectivePipelineSettings,
|
||||
"Conig loaded pipeline settings for index 0 not of expected type ReflectivePipelineSettings!");
|
||||
Assertions.assertTrue(
|
||||
coloredShapePipelineSettings instanceof ColoredShapePipelineSettings,
|
||||
"Conig loaded pipeline settings for index 1 not of expected type ColoredShapePipelineSettings!");
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanup() {
|
||||
try {
|
||||
Files.deleteIfExists(Paths.get("settings.json"));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
new File(ConfigManager.getRootFolder().toAbsolutePath().toString()).delete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user