mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-21 01:01:41 +00:00
Rename to PhotonVision
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package org.photonvision.common.util;
|
||||
|
||||
import java.awt.*;
|
||||
import org.opencv.core.Scalar;
|
||||
|
||||
public class ColorHelper {
|
||||
public static Scalar colorToScalar(Color color) {
|
||||
return new Scalar(color.getBlue(), color.getGreen(), color.getRed());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.photonvision.common.util;
|
||||
|
||||
/** A thread that tries to run at a specified loop time */
|
||||
public abstract class LoopingRunnable implements Runnable {
|
||||
protected volatile Long loopTimeMs;
|
||||
|
||||
protected abstract void process();
|
||||
|
||||
public LoopingRunnable(Long loopTimeMs) {
|
||||
this.loopTimeMs = loopTimeMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!Thread.interrupted()) {
|
||||
var now = System.currentTimeMillis();
|
||||
|
||||
// Do the thing
|
||||
process();
|
||||
|
||||
// sleep for the remaining time
|
||||
var timeElapsed = System.currentTimeMillis() - now;
|
||||
var delta = loopTimeMs - timeElapsed;
|
||||
try {
|
||||
if (delta > 0.0) {
|
||||
|
||||
Thread.sleep(delta, 0);
|
||||
|
||||
} else {
|
||||
Thread.sleep(1);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.photonvision.common.util;
|
||||
|
||||
public class MemoryManager {
|
||||
|
||||
private static final long MEGABYTE_FACTOR = 1024L * 1024L;
|
||||
|
||||
private int collectionThreshold;
|
||||
private long collectionPeriodMillis = -1;
|
||||
|
||||
private double lastUsedMb = 0;
|
||||
private long lastCollectionMillis = 0;
|
||||
|
||||
public MemoryManager(int collectionThreshold) {
|
||||
this.collectionThreshold = collectionThreshold;
|
||||
}
|
||||
|
||||
public MemoryManager(int collectionThreshold, long collectionPeriodMillis) {
|
||||
this.collectionThreshold = collectionThreshold;
|
||||
this.collectionPeriodMillis = collectionPeriodMillis;
|
||||
}
|
||||
|
||||
public void setCollectionThreshold(int collectionThreshold) {
|
||||
this.collectionThreshold = collectionThreshold;
|
||||
}
|
||||
|
||||
public void setCollectionPeriodMillis(long collectionPeriodMillis) {
|
||||
this.collectionPeriodMillis = collectionPeriodMillis;
|
||||
}
|
||||
|
||||
private static long getUsedMemory() {
|
||||
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
}
|
||||
|
||||
private static double getUsedMemoryMB() {
|
||||
return ((double) getUsedMemory() / MEGABYTE_FACTOR);
|
||||
}
|
||||
|
||||
private void collect() {
|
||||
System.gc();
|
||||
System.runFinalization();
|
||||
}
|
||||
|
||||
public void run() {
|
||||
run(false);
|
||||
}
|
||||
|
||||
public void run(boolean print) {
|
||||
var usedMem = getUsedMemoryMB();
|
||||
|
||||
if (usedMem != lastUsedMb) {
|
||||
lastUsedMb = usedMem;
|
||||
if (print) System.out.printf("Memory usage: %.2fMB\n", usedMem);
|
||||
}
|
||||
|
||||
boolean collectionThresholdPassed = usedMem >= collectionThreshold;
|
||||
boolean collectionPeriodPassed =
|
||||
collectionPeriodMillis != -1
|
||||
&& (System.currentTimeMillis() - lastCollectionMillis >= collectionPeriodMillis);
|
||||
|
||||
if (collectionThresholdPassed || collectionPeriodPassed) {
|
||||
collect();
|
||||
lastCollectionMillis = System.currentTimeMillis();
|
||||
if (print) {
|
||||
System.out.printf("Garbage collected at %.2fMB\n", usedMem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package org.photonvision.common.util;
|
||||
|
||||
import edu.wpi.first.wpiutil.RuntimeDetector;
|
||||
import java.io.IOException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum Platform {
|
||||
// WPILib Supported (JNI)
|
||||
WINDOWS_32("Windows x32"),
|
||||
WINDOWS_64("Windows x64"),
|
||||
LINUX_64("Linux x64"),
|
||||
LINUX_RASPBIAN("Linux Raspbian"), // Raspberry Pi 3/4
|
||||
LINUX_AARCH64BIONIC("Linux AARCH64 Bionic"), // Jetson Nano, Jetson TX2
|
||||
MACOS_64("Mac OS x64"),
|
||||
|
||||
// ChameleonVision Supported (Manual install)
|
||||
LINUX_ARM32("Linux ARM32"), // ODROID XU4, C1+
|
||||
LINUX_ARM64("Linux ARM64"), // ODROID C2, N2
|
||||
|
||||
// Completely unsupported
|
||||
UNSUPPORTED("Unsupported Platform");
|
||||
|
||||
public final String value;
|
||||
public final boolean isRoot = checkForRoot();
|
||||
|
||||
Platform(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private static final String OS_NAME = System.getProperty("os.name");
|
||||
private static final String OS_ARCH = System.getProperty("os.arch");
|
||||
public static final Platform CurrentPlatform = getCurrentPlatform();
|
||||
|
||||
private static String UnknownPlatformString =
|
||||
String.format("Unknown Platform. OS: %s, Architecture: %s", OS_NAME, OS_ARCH);
|
||||
|
||||
public boolean isWindows() {
|
||||
return this == WINDOWS_64 || this == WINDOWS_32;
|
||||
}
|
||||
|
||||
public boolean isLinux() {
|
||||
return this == LINUX_64 || this == LINUX_RASPBIAN || this == LINUX_ARM64;
|
||||
}
|
||||
|
||||
public boolean isMac() {
|
||||
return this == MACOS_64;
|
||||
}
|
||||
|
||||
public static boolean isRaspberryPi() {
|
||||
return CurrentPlatform.equals(LINUX_RASPBIAN);
|
||||
}
|
||||
|
||||
private static ShellExec shell = new ShellExec(true, false);
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
private boolean checkForRoot() {
|
||||
if (isLinux() || isMac()) {
|
||||
try {
|
||||
shell.execute("id", null, true, "-u");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
while (!shell.isOutputCompleted()) {
|
||||
// TODO: add timeout
|
||||
}
|
||||
|
||||
if (shell.getExitCode() == 0) {
|
||||
return shell.getOutput().split("\n")[0].equals("0");
|
||||
}
|
||||
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Platform getCurrentPlatform() {
|
||||
if (RuntimeDetector.isWindows()) {
|
||||
if (RuntimeDetector.is32BitIntel()) return WINDOWS_32;
|
||||
if (RuntimeDetector.is64BitIntel()) return WINDOWS_64;
|
||||
}
|
||||
|
||||
if (RuntimeDetector.isMac()) {
|
||||
if (RuntimeDetector.is32BitIntel()) return UNSUPPORTED;
|
||||
if (RuntimeDetector.is64BitIntel()) return MACOS_64;
|
||||
}
|
||||
|
||||
if (RuntimeDetector.isLinux()) {
|
||||
if (RuntimeDetector.is32BitIntel()) return UNSUPPORTED;
|
||||
if (RuntimeDetector.is64BitIntel()) return LINUX_64;
|
||||
if (RuntimeDetector.isRaspbian()) return LINUX_RASPBIAN;
|
||||
}
|
||||
|
||||
System.out.println(UnknownPlatformString);
|
||||
return Platform.UNSUPPORTED;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
if (this.equals(UNSUPPORTED)) {
|
||||
return UnknownPlatformString;
|
||||
} else {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.photonvision.common.util;
|
||||
|
||||
public class ReflectionUtils {
|
||||
|
||||
public static StackTraceElement[] getFullStackTrace() {
|
||||
return Thread.currentThread().getStackTrace();
|
||||
}
|
||||
|
||||
public static StackTraceElement getNthCaller(int n) {
|
||||
if (n < 0) n = 0;
|
||||
return Thread.currentThread().getStackTrace()[n];
|
||||
}
|
||||
|
||||
public static String getCallerClassName() {
|
||||
StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
|
||||
for (int i = 1; i < stElements.length; i++) {
|
||||
StackTraceElement ste = stElements[i];
|
||||
if (!ste.getClassName().equals(ReflectionUtils.class.getName())
|
||||
&& ste.getClassName().indexOf("java.lang.Thread") != 0) {
|
||||
return ste.getClassName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getCallerCallerClassName() {
|
||||
StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
|
||||
String callerClassName = null;
|
||||
for (int i = 1; i < stElements.length; i++) {
|
||||
StackTraceElement ste = stElements[i];
|
||||
if (!ste.getClassName().equals(ReflectionUtils.class.getName())
|
||||
&& ste.getClassName().indexOf("java.lang.Thread") != 0) {
|
||||
if (callerClassName == null) {
|
||||
callerClassName = ste.getClassName();
|
||||
} else if (!callerClassName.equals(ste.getClassName())) {
|
||||
return ste.getClassName();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package org.photonvision.common.util;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/** Execute external process and optionally read output buffer. */
|
||||
@SuppressWarnings({"unused", "ConstantConditions"})
|
||||
public class ShellExec {
|
||||
private int exitCode;
|
||||
private boolean readOutput, readError;
|
||||
private StreamGobbler errorGobbler, outputGobbler;
|
||||
|
||||
public ShellExec() {
|
||||
this(false, false);
|
||||
}
|
||||
|
||||
public ShellExec(boolean readOutput, boolean readError) {
|
||||
this.readOutput = readOutput;
|
||||
this.readError = readError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a bash command. We can handle complex bash commands including multiple executions (; |
|
||||
* && ||), quotes, expansions ($), escapes (\), e.g.: "cd /abc/def; mv ghi 'older ghi '$(whoami)"
|
||||
*
|
||||
* @param command Bash command to execute
|
||||
* @return true if bash got started, but your command may have failed.
|
||||
*/
|
||||
public int executeBashCommand(String command) throws IOException {
|
||||
boolean wait = true;
|
||||
boolean success = false;
|
||||
Runtime r = Runtime.getRuntime();
|
||||
// Use bash -c so we can handle things like multi commands separated by ; and
|
||||
// things like quotes, $, |, and \. My tests show that command comes as
|
||||
// one argument to bash, so we do not need to quote it to make it one thing.
|
||||
// Also, exec may object if it does not have an executable file as the first thing,
|
||||
// so having bash here makes it happy provided bash is installed and in path.
|
||||
String[] commands = {"bash", "-c", command};
|
||||
|
||||
Process process = r.exec(commands);
|
||||
|
||||
// Consume streams, older jvm's had a memory leak if streams were not read,
|
||||
// some other jvm+OS combinations may block unless streams are consumed.
|
||||
return doProcess(wait, process);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in current folder, and wait for process to end
|
||||
*
|
||||
* @param command command ("c:/some/folder/script.bat" or "some/folder/script.sh")
|
||||
* @param args 0..n command line arguments
|
||||
* @return process exit code
|
||||
*/
|
||||
public int execute(String command, String... args) throws IOException {
|
||||
return execute(command, null, true, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command.
|
||||
*
|
||||
* @param command command ("c:/some/folder/script.bat" or "some/folder/script.sh")
|
||||
* @param workdir working directory or NULL to use command folder
|
||||
* @param wait wait for process to end
|
||||
* @param args 0..n command line arguments
|
||||
* @return process exit code
|
||||
*/
|
||||
public int execute(String command, String workdir, boolean wait, String... args)
|
||||
throws IOException {
|
||||
String[] cmdArr;
|
||||
if (args != null && args.length > 0) {
|
||||
cmdArr = new String[1 + args.length];
|
||||
cmdArr[0] = command;
|
||||
System.arraycopy(args, 0, cmdArr, 1, args.length);
|
||||
} else {
|
||||
cmdArr = new String[] {command};
|
||||
}
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder(cmdArr);
|
||||
File workingDir = (workdir == null ? new File(command).getParentFile() : new File(workdir));
|
||||
pb.directory(workingDir);
|
||||
|
||||
Process process = pb.start();
|
||||
|
||||
// Consume streams, older jvm's had a memory leak if streams were not read,
|
||||
// some other jvm+OS combinations may block unless streams are consumed.
|
||||
return doProcess(wait, process);
|
||||
}
|
||||
|
||||
private int doProcess(boolean wait, Process process) {
|
||||
errorGobbler = new StreamGobbler(process.getErrorStream(), readError);
|
||||
outputGobbler = new StreamGobbler(process.getInputStream(), readOutput);
|
||||
errorGobbler.start();
|
||||
outputGobbler.start();
|
||||
|
||||
exitCode = 0;
|
||||
if (wait) {
|
||||
try {
|
||||
process.waitFor();
|
||||
exitCode = process.exitValue();
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
public int getExitCode() {
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
public boolean isOutputCompleted() {
|
||||
return (outputGobbler != null && outputGobbler.isCompleted());
|
||||
}
|
||||
|
||||
public boolean isErrorCompleted() {
|
||||
return (errorGobbler != null && errorGobbler.isCompleted());
|
||||
}
|
||||
|
||||
public String getOutput() {
|
||||
return (outputGobbler != null ? outputGobbler.getOutput() : null);
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return (errorGobbler != null ? errorGobbler.getOutput() : null);
|
||||
}
|
||||
|
||||
// ********************************************
|
||||
// ********************************************
|
||||
|
||||
/**
|
||||
* StreamGobbler reads inputstream to "gobble" it. This is used by Executor class when running a
|
||||
* commandline applications. Gobblers must read/purge INSTR and ERRSTR process streams.
|
||||
* http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html?page=4
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
private static class StreamGobbler extends Thread {
|
||||
private InputStream is;
|
||||
private StringBuilder output;
|
||||
private volatile boolean completed; // mark volatile to guarantee a thread safety
|
||||
|
||||
public StreamGobbler(InputStream is, boolean readStream) {
|
||||
this.is = is;
|
||||
this.output = (readStream ? new StringBuilder(256) : null);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
completed = false;
|
||||
try {
|
||||
String NL = System.getProperty("line.separator", "\r\n");
|
||||
|
||||
InputStreamReader isr = new InputStreamReader(is);
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
if (output != null) output.append(line).append(NL);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// ex.printStackTrace();
|
||||
}
|
||||
completed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inputstream buffer or null if stream was not consumed.
|
||||
*
|
||||
* @return Output stream
|
||||
*/
|
||||
public String getOutput() {
|
||||
return (output != null ? output.toString() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is input stream completed.
|
||||
*
|
||||
* @return if input stream is completed
|
||||
*/
|
||||
public boolean isCompleted() {
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package org.photonvision.common.util;
|
||||
|
||||
import edu.wpi.cscore.CameraServerCvJNI;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.highgui.HighGui;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum WPI2019Image {
|
||||
kCargoAngledDark48in(1.2192),
|
||||
kCargoSideStraightDark36in(0.9144),
|
||||
kCargoSideStraightDark60in(1.524),
|
||||
kCargoSideStraightDark72in(1.8288),
|
||||
kCargoSideStraightPanelDark36in(0.9144),
|
||||
kCargoStraightDark19in(0.4826),
|
||||
kCargoStraightDark24in(0.6096),
|
||||
kCargoStraightDark48in(1.2192),
|
||||
kCargoStraightDark72in(1.8288),
|
||||
kCargoStraightDark72in_HighRes(1.8288),
|
||||
kCargoStraightDark90in(2.286);
|
||||
|
||||
public static double FOV = 68.5;
|
||||
|
||||
public final double distanceMeters;
|
||||
public final Path path;
|
||||
|
||||
Path getPath() {
|
||||
var filename = this.toString().substring(1);
|
||||
return Path.of("2019", "WPI", filename + ".jpg");
|
||||
}
|
||||
|
||||
WPI2019Image(double distanceMeters) {
|
||||
this.distanceMeters = distanceMeters;
|
||||
this.path = getPath();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public enum WPI2020Image {
|
||||
kBlueGoal_060in_Center(1.524),
|
||||
kBlueGoal_084in_Center(2.1336),
|
||||
kBlueGoal_084in_Center_720p(2.1336),
|
||||
kBlueGoal_108in_Center(2.7432),
|
||||
kBlueGoal_132in_Center(3.3528),
|
||||
kBlueGoal_156in_Center(3.9624),
|
||||
kBlueGoal_180in_Center(4.572),
|
||||
kBlueGoal_156in_Left(3.9624),
|
||||
kBlueGoal_224in_Left(5.6896),
|
||||
kBlueGoal_228in_ProtectedZone(5.7912),
|
||||
kBlueGoal_330in_ProtectedZone(8.382),
|
||||
kBlueGoal_Far_ProtectedZone(10.668), // TODO: find a more accurate distance
|
||||
kRedLoading_016in_Down(0.4064),
|
||||
kRedLoading_030in_Down(0.762),
|
||||
kRedLoading_048in_Down(1.2192),
|
||||
kRedLoading_048in(1.2192),
|
||||
kRedLoading_060in(1.524),
|
||||
kRedLoading_084in(2.1336),
|
||||
kRedLoading_108in(2.7432);
|
||||
|
||||
public static double FOV = 68.5;
|
||||
|
||||
public final double distanceMeters;
|
||||
public final Path path;
|
||||
|
||||
Path getPath() {
|
||||
var filename = this.toString().substring(1).replace('_', '-');
|
||||
return Path.of("2020", "WPI", filename + ".jpg");
|
||||
}
|
||||
|
||||
WPI2020Image(double distanceMeters) {
|
||||
this.distanceMeters = distanceMeters;
|
||||
this.path = getPath();
|
||||
}
|
||||
}
|
||||
|
||||
public enum PolygonTestImages {
|
||||
kPolygons;
|
||||
|
||||
public final Path path;
|
||||
|
||||
Path getPath() {
|
||||
var filename = this.toString().substring(1).toLowerCase();
|
||||
return Path.of("polygons", filename + ".png");
|
||||
}
|
||||
|
||||
PolygonTestImages() {
|
||||
this.path = getPath();
|
||||
}
|
||||
}
|
||||
|
||||
public enum PowercellTestImages {
|
||||
kPowercell_test_1,
|
||||
kPowercell_test_2,
|
||||
kPowercell_test_3,
|
||||
kPowercell_test_4,
|
||||
kPowercell_test_5,
|
||||
kPowercell_test_6;
|
||||
|
||||
public final Path path;
|
||||
|
||||
Path getPath() {
|
||||
var filename = this.toString().substring(1).toLowerCase();
|
||||
return Path.of(filename + ".png");
|
||||
}
|
||||
|
||||
PowercellTestImages() {
|
||||
this.path = getPath();
|
||||
}
|
||||
}
|
||||
|
||||
private static Path getResourcesFolderPath() {
|
||||
return Path.of("src", "test", "resources").toAbsolutePath();
|
||||
}
|
||||
|
||||
public static Path getTestImagesPath() {
|
||||
return getResourcesFolderPath().resolve("testimages");
|
||||
}
|
||||
|
||||
public static Path getCalibrationPath() {
|
||||
return getResourcesFolderPath().resolve("calibration");
|
||||
}
|
||||
|
||||
public static Path getPowercellPath() {
|
||||
return getTestImagesPath().resolve("polygons").resolve("powercells");
|
||||
}
|
||||
|
||||
public static Path getWPIImagePath(WPI2020Image image) {
|
||||
return getTestImagesPath().resolve(image.path);
|
||||
}
|
||||
|
||||
public static Path getWPIImagePath(WPI2019Image image) {
|
||||
return getTestImagesPath().resolve(image.path);
|
||||
}
|
||||
|
||||
public static Path getPolygonImagePath(PolygonTestImages image) {
|
||||
return getTestImagesPath().resolve(image.path);
|
||||
}
|
||||
|
||||
public static Path getPowercellImagePath(PowercellTestImages image) {
|
||||
return getPowercellPath().resolve(image.path);
|
||||
}
|
||||
|
||||
public static void loadLibraries() {
|
||||
try {
|
||||
CameraServerCvJNI.forceLoad();
|
||||
} catch (IOException e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static int DefaultTimeoutMillis = 5000;
|
||||
|
||||
public static void showImage(Mat frame, String title, int timeoutMs) {
|
||||
try {
|
||||
HighGui.imshow(title, frame);
|
||||
HighGui.waitKey(timeoutMs);
|
||||
HighGui.destroyAllWindows();
|
||||
} catch (HeadlessException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void showImage(Mat frame, int timeoutMs) {
|
||||
showImage(frame, "", timeoutMs);
|
||||
}
|
||||
|
||||
public static void showImage(Mat frame, String title) {
|
||||
showImage(frame, title, DefaultTimeoutMillis);
|
||||
}
|
||||
|
||||
public static void showImage(Mat frame) {
|
||||
showImage(frame, DefaultTimeoutMillis);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.photonvision.common.util.file;
|
||||
|
||||
import org.photonvision.common.logging.LogGroup;
|
||||
import org.photonvision.common.logging.Logger;
|
||||
import org.photonvision.common.util.Platform;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFileAttributes;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
private FileUtils() {}
|
||||
|
||||
private static Logger logger = new Logger(FileUtils.class, LogGroup.General);
|
||||
private static final Set<PosixFilePermission> allReadWriteExecutePerms =
|
||||
new HashSet<>(Arrays.asList(PosixFilePermission.values()));
|
||||
|
||||
public static void setFilePerms(Path path) throws IOException {
|
||||
if (!Platform.CurrentPlatform.isWindows()) {
|
||||
File thisFile = path.toFile();
|
||||
Set<PosixFilePermission> perms =
|
||||
Files.readAttributes(path, PosixFileAttributes.class).permissions();
|
||||
if (!perms.equals(allReadWriteExecutePerms)) {
|
||||
logger.info("Setting perms on" + path.toString());
|
||||
Files.setPosixFilePermissions(path, perms);
|
||||
if (thisFile.isDirectory()) {
|
||||
for (File subfile : thisFile.listFiles()) {
|
||||
setFilePerms(subfile.toPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAllPerms(Path path) {
|
||||
if (!Platform.CurrentPlatform.isWindows()) {
|
||||
String command = String.format("chmod 777 -R %s", path.toString());
|
||||
try {
|
||||
Process p = Runtime.getRuntime().exec(command);
|
||||
p.waitFor();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
logger.info("Cannot set directory permissions on Windows!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.photonvision.common.util.file;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
|
||||
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class JacksonUtils {
|
||||
public static <T> void serializer(Path path, T object) throws IOException {
|
||||
serializer(path, object, false);
|
||||
}
|
||||
|
||||
public static <T> void serializer(Path path, T object, boolean forceSync) throws IOException {
|
||||
PolymorphicTypeValidator ptv =
|
||||
BasicPolymorphicTypeValidator.builder().allowIfBaseType(object.getClass()).build();
|
||||
ObjectMapper objectMapper =
|
||||
JsonMapper.builder()
|
||||
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
|
||||
.build();
|
||||
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
||||
saveJsonString(json, path, forceSync);
|
||||
}
|
||||
|
||||
public static <T> T deserialize(Path path, Class<T> ref) throws IOException {
|
||||
PolymorphicTypeValidator ptv =
|
||||
BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
|
||||
ObjectMapper objectMapper =
|
||||
JsonMapper.builder()
|
||||
.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT)
|
||||
.build();
|
||||
File jsonFile = new File(path.toString());
|
||||
if (jsonFile.exists() && jsonFile.length() > 0) {
|
||||
return objectMapper.readValue(jsonFile, ref);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> T deserialize(Path path, Class<T> ref, StdDeserializer<T> deserializer)
|
||||
throws IOException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addDeserializer(ref, deserializer);
|
||||
objectMapper.registerModule(module);
|
||||
|
||||
File jsonFile = new File(path.toString());
|
||||
if (jsonFile.exists() && jsonFile.length() > 0) {
|
||||
return objectMapper.readValue(jsonFile, ref);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> void serialize(Path path, T object, Class<T> ref, StdSerializer<T> serializer)
|
||||
throws IOException {
|
||||
serialize(path, object, ref, serializer, false);
|
||||
}
|
||||
|
||||
public static <T> void serialize(
|
||||
Path path, T object, Class<T> ref, StdSerializer<T> serializer, boolean forceSync)
|
||||
throws IOException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.addSerializer(ref, serializer);
|
||||
objectMapper.registerModule(module);
|
||||
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
|
||||
saveJsonString(json, path, forceSync);
|
||||
}
|
||||
|
||||
private static void saveJsonString(String json, Path path, boolean forceSync) throws IOException {
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(path.toFile());
|
||||
fileOutputStream.write(json.getBytes());
|
||||
fileOutputStream.flush();
|
||||
if (forceSync) {
|
||||
FileDescriptor fileDescriptor = fileOutputStream.getFD();
|
||||
fileDescriptor.sync();
|
||||
}
|
||||
fileOutputStream.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.photonvision.common.util.math;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class IPUtils {
|
||||
public static boolean isValidIPV4(final String ip) {
|
||||
String PATTERN =
|
||||
"^((0|1\\d?\\d?|2[0-4]?\\d?|25[0-5]?|[3-9]\\d?)\\.){3}(0|1\\d?\\d?|2[0-4]?\\d?|25[0-5]?|[3-9]\\d?)$";
|
||||
|
||||
return ip.matches(PATTERN);
|
||||
}
|
||||
|
||||
public static List<Byte> getDigitBytes(int num) {
|
||||
List<Byte> digits = new ArrayList<>();
|
||||
collectDigitBytes(num, digits);
|
||||
return digits;
|
||||
}
|
||||
|
||||
private static void collectDigitBytes(int num, List<Byte> digits) {
|
||||
if (num / 10 > 0) {
|
||||
collectDigitBytes(num / 10, digits);
|
||||
}
|
||||
digits.add((byte) (num % 10));
|
||||
}
|
||||
|
||||
public static List<Integer> getDigits(int num) {
|
||||
List<Integer> digits = new ArrayList<>();
|
||||
collectDigits(num, digits);
|
||||
return digits;
|
||||
}
|
||||
|
||||
private static void collectDigits(int num, List<Integer> digits) {
|
||||
if (num / 10 > 0) {
|
||||
collectDigits(num / 10, digits);
|
||||
}
|
||||
digits.add(num % 10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.photonvision.common.util.math;
|
||||
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
public class MathUtils {
|
||||
MathUtils() {}
|
||||
|
||||
public static double sigmoid(Number x) {
|
||||
double bias = 0;
|
||||
double a = 5;
|
||||
double b = -0.05;
|
||||
double k = 200;
|
||||
|
||||
if (x.doubleValue() < 50) {
|
||||
bias = -1.338;
|
||||
}
|
||||
|
||||
return ((k / (1 + Math.pow(Math.E, (a + (b * x.doubleValue()))))) + bias);
|
||||
}
|
||||
|
||||
public static double toSlope(Number angle) {
|
||||
return FastMath.atan(FastMath.toRadians(angle.doubleValue() - 90));
|
||||
}
|
||||
|
||||
public static double roundTo(double value, int to) {
|
||||
double toMult = Math.pow(10, to);
|
||||
return (double) Math.round(value * toMult) / toMult;
|
||||
}
|
||||
|
||||
public static double nanosToMillis(long nanos) {
|
||||
return nanos / 1000000.0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.photonvision.common.util.numbers;
|
||||
|
||||
import org.opencv.core.Point;
|
||||
|
||||
public class DoubleCouple extends NumberCouple<Double> {
|
||||
|
||||
public DoubleCouple() {
|
||||
super(0.0, 0.0);
|
||||
}
|
||||
|
||||
public DoubleCouple(Double first, Double second) {
|
||||
super(first, second);
|
||||
}
|
||||
|
||||
public DoubleCouple(Point point) {
|
||||
super(point.x, point.y);
|
||||
}
|
||||
|
||||
public Point toPoint() {
|
||||
return new Point(first, second);
|
||||
}
|
||||
|
||||
public void fromPoint(Point point) {
|
||||
first = point.x;
|
||||
second = point.y;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.photonvision.common.util.numbers;
|
||||
|
||||
public class IntegerCouple extends NumberCouple<Integer> {
|
||||
|
||||
public IntegerCouple() {
|
||||
super(0, 0);
|
||||
}
|
||||
|
||||
public IntegerCouple(Integer first, Integer second) {
|
||||
super(first, second);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.photonvision.common.util.numbers;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public abstract class NumberCouple<T extends Number> {
|
||||
|
||||
protected T first;
|
||||
protected T second;
|
||||
|
||||
public NumberCouple(T first, T second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public void setFirst(T first) {
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public T getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public void setSecond(T second) {
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public T getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public void set(T first, T second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof NumberCouple)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var couple = (NumberCouple) obj;
|
||||
if (!couple.first.equals(first)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!couple.second.equals(second)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isEmpty() {
|
||||
return first.intValue() == 0 && second.intValue() == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package org.photonvision.common.util.numbers;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class NumberListUtils {
|
||||
|
||||
/**
|
||||
* @param collection an ArrayList of Comparable objects
|
||||
* @return the median of collection
|
||||
*/
|
||||
public static <T extends Number> double median(List<T> collection, Comparator<T> comp) {
|
||||
double result;
|
||||
int n = collection.size() / 2;
|
||||
|
||||
if (collection.size() % 2 == 0) // even number of items; find the middle two and average them
|
||||
result =
|
||||
(nthSmallest(collection, n - 1, comp).doubleValue()
|
||||
+ nthSmallest(collection, n, comp).doubleValue())
|
||||
/ 2.0;
|
||||
else // odd number of items; return the one in the middle
|
||||
result = nthSmallest(collection, n, comp).doubleValue();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T extends Number> String toString(List<T> collection) {
|
||||
return toString(collection, "");
|
||||
}
|
||||
|
||||
public static <T extends Number> String toString(List<T> collection, String suffix) {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (T x : collection) {
|
||||
String s = x.doubleValue() + suffix;
|
||||
joiner.add(s);
|
||||
}
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param collection an ArrayList of Numbers
|
||||
* @return the mean of collection
|
||||
*/
|
||||
public static double mean(final List<? extends Number> collection) {
|
||||
BigDecimal sum = BigDecimal.ZERO;
|
||||
for (final Number number : collection) {
|
||||
sum = sum.add(BigDecimal.valueOf(number.doubleValue()));
|
||||
}
|
||||
return (sum.doubleValue() / collection.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param collection a collection of Comparable objects
|
||||
* @param n the position of the desired object, using the ordering defined on the collection
|
||||
* elements
|
||||
* @return the nth smallest object
|
||||
*/
|
||||
public static <T> T nthSmallest(List<T> collection, int n, Comparator<T> comp) {
|
||||
T result, pivot;
|
||||
ArrayList<T> underPivot = new ArrayList<>(),
|
||||
overPivot = new ArrayList<>(),
|
||||
equalPivot = new ArrayList<>();
|
||||
|
||||
// choosing a pivot is a whole topic in itself.
|
||||
// this implementation uses the simple strategy of grabbing something from the middle of the
|
||||
// ArrayList.
|
||||
|
||||
pivot = collection.get(n / 2);
|
||||
|
||||
// split collection into 3 lists based on comparison with the pivot
|
||||
|
||||
for (T obj : collection) {
|
||||
int order = comp.compare(obj, pivot);
|
||||
|
||||
if (order < 0) // obj < pivot
|
||||
underPivot.add(obj);
|
||||
else if (order > 0) // obj > pivot
|
||||
overPivot.add(obj);
|
||||
else // obj = pivot
|
||||
equalPivot.add(obj);
|
||||
} // for each obj in collection
|
||||
|
||||
// recurse on the appropriate collection
|
||||
|
||||
if (n < underPivot.size()) result = nthSmallest(underPivot, n, comp);
|
||||
else if (n < underPivot.size() + equalPivot.size()) // equal to pivot; just return it
|
||||
result = pivot;
|
||||
else // everything in underPivot and equalPivot is too small. Adjust n accordingly in the
|
||||
// recursion.
|
||||
result = nthSmallest(overPivot, n - underPivot.size() - equalPivot.size(), comp);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user