* 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:
Matt
2020-06-07 12:25:18 -07:00
committed by GitHub
parent 0664c02890
commit 0b09e72771
22 changed files with 654 additions and 47 deletions

2
.gitignore vendored
View File

@@ -122,4 +122,4 @@ New client/chameleon-client/*
.DS_Store
# *.iml
chameleon-server/build
chameleon-server/chameleon-vision (1).iml
chameleon-server/chameleon-vision

View File

@@ -2,4 +2,5 @@ bin/*
.settings/*
.project
.classpath
*.prefs
*.prefs
chameleonVision

View File

@@ -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"

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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) {}
}

View File

@@ -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) {}
}

View File

@@ -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;
}
}

View File

@@ -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
}

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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();
}
}