Merge branch 'Java' into 'dev'
java translation complete See merge request chameleon-vision/Chameleon-Vision!12
99
.gitignore
vendored
@@ -12,3 +12,102 @@ Python/app/handlers/__pycache__/
|
||||
|
||||
backend/settings/
|
||||
/.vscode/
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
Main/Settings/
|
||||
Main/.gradle
|
||||
Main/target
|
||||
|
||||
New client/chameleon-client/node_modules/
|
||||
Main/dependency-reduced-pom.xml
|
||||
Main/src/main/java/META-INF
|
||||
|
||||
64
Main/chameleon-vision.iml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_12">
|
||||
<output url="file://$MODULE_DIR$/target/classes" />
|
||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Maven: io.javalin:javalin:3.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.31" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.3.31" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-common:1.3.31" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains:annotations:13.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.31" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.26" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-server:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:3.1.0" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-http:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-util:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-io:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-webapp:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-xml:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-servlet:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-security:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-server:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-common:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-api:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-client:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-client:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.eclipse.jetty.websocket:websocket-servlet:9.4.19.v20190610" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.json:json:20190722" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-simple:1.7.26" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.commons:commons-math3:3.6.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.google.code.gson:gson:2.8.5" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.1.9.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.1.9.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.1.9.RELEASE" level="project" />
|
||||
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.9" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-java:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxathena:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxraspbian:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxx86-64:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:windowsx86-64:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.cameraserver:cameraserver-java:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-java:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxathena:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxraspbian:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxx86-64:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:windowsx86-64:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.wpiutil:wpiutil-java:2019.4.1" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-java:3.4.4-5" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-jni:windowsx86-64:3.4.4-5" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-jni:linuxx86-64:3.4.4-5" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-jni:linuxathena:3.4.4-5" level="project" />
|
||||
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2019.opencv:opencv-jni:linuxraspbian:3.4.4-5" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.10.0.pr1" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.10.0.pr1" level="project" />
|
||||
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.10.0.pr1" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
220
Main/pom.xml
Normal file
@@ -0,0 +1,220 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.chameleon-vision.main</groupId>
|
||||
<artifactId>chameleon-vision</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<build>
|
||||
<plugins>
|
||||
<!--setup for java jdk 12-->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<release>12</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>com.chameleonvision.Main</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<repositories>
|
||||
<!--WPI official maven repo for frc libs-->
|
||||
<repository>
|
||||
<id>WPI</id>
|
||||
<name>WPI Maven repo</name>
|
||||
<url>https://first.wpi.edu/FRC/roborio/maven/release</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<dependencies>
|
||||
<!--javalin micro webservices framework-->
|
||||
<dependency>
|
||||
<groupId>io.javalin</groupId>
|
||||
<artifactId>javalin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
</dependency>
|
||||
|
||||
<!--org.json from saving and loading data-->
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20190722</version>
|
||||
</dependency>
|
||||
|
||||
<!--slf4j for javalin -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.7.26</version>
|
||||
</dependency>
|
||||
|
||||
<!--apache common classes libs-->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-math3</artifactId>
|
||||
<version>3.6.1</version>
|
||||
</dependency>
|
||||
<!--google json save and load library-->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.5</version>
|
||||
</dependency>
|
||||
<!--what is this for again?-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-beans</artifactId>
|
||||
<version>5.1.9.RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.9</version>
|
||||
</dependency>
|
||||
|
||||
<!-- supported platforms for wpilib JNI classifiers
|
||||
linuxathena
|
||||
linuxraspbian
|
||||
linuxx86-64
|
||||
windowsx86-64
|
||||
-->
|
||||
<!--frc cscore java libs-->
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.cscore</groupId>
|
||||
<artifactId>cscore-java</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
</dependency>
|
||||
<!--frc cscore interface libs-->
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.cscore</groupId>
|
||||
<artifactId>cscore-jni</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
<classifier>linuxathena</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.cscore</groupId>
|
||||
<artifactId>cscore-jni</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
<classifier>linuxraspbian</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.cscore</groupId>
|
||||
<artifactId>cscore-jni</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
<classifier>linuxx86-64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.cscore</groupId>
|
||||
<artifactId>cscore-jni</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
<classifier>windowsx86-64</classifier>
|
||||
</dependency>
|
||||
|
||||
<!--frc camera server libs-->
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.cameraserver</groupId>
|
||||
<artifactId>cameraserver-java</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
</dependency>
|
||||
|
||||
<!--frc network table java libs-->
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.ntcore</groupId>
|
||||
<artifactId>ntcore-java</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
</dependency>
|
||||
|
||||
<!--frc network tables interface libs-->
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.ntcore</groupId>
|
||||
<artifactId>ntcore-jni</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
<classifier>linuxathena</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.ntcore</groupId>
|
||||
<artifactId>ntcore-jni</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
<classifier>linuxraspbian</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.ntcore</groupId>
|
||||
<artifactId>ntcore-jni</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
<classifier>linuxx86-64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.ntcore</groupId>
|
||||
<artifactId>ntcore-jni</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
<classifier>windowsx86-64</classifier>
|
||||
</dependency>
|
||||
|
||||
<!--frc java libs-->
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.wpiutil</groupId>
|
||||
<artifactId>wpiutil-java</artifactId>
|
||||
<version>2019.4.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- WPI OpenCV for all supported platforms -->
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
|
||||
<artifactId>opencv-java</artifactId>
|
||||
<version>3.4.4-5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
|
||||
<artifactId>opencv-jni</artifactId>
|
||||
<version>3.4.4-5</version>
|
||||
<classifier>windowsx86-64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
|
||||
<artifactId>opencv-jni</artifactId>
|
||||
<version>3.4.4-5</version>
|
||||
<classifier>linuxx86-64</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
|
||||
<artifactId>opencv-jni</artifactId>
|
||||
<version>3.4.4-5</version>
|
||||
<classifier>linuxathena</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>edu.wpi.first.thirdparty.frc2019.opencv</groupId>
|
||||
<artifactId>opencv-jni</artifactId>
|
||||
<version>3.4.4-5</version>
|
||||
<classifier>linuxraspbian</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.10.0.pr1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
25
Main/src/main/java/com/chameleonvision/CameraException.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.chameleonvision;
|
||||
|
||||
public class CameraException extends Exception {
|
||||
public enum CameraExceptionType {
|
||||
NO_CAMERA,
|
||||
BAD_CAMERA,
|
||||
BAD_PIPELINE,
|
||||
BAD_SETTING;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (this) {
|
||||
case NO_CAMERA: return "No camera connected!";
|
||||
case BAD_CAMERA: return "Invalid camera!";
|
||||
case BAD_PIPELINE: return "Invalid pipeline!";
|
||||
case BAD_SETTING: return "Invalid camera/pipeline setting!";
|
||||
default: return "Unknown camera exception!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CameraException(CameraExceptionType camExceptionType) {
|
||||
super(camExceptionType.toString());
|
||||
}
|
||||
}
|
||||
25
Main/src/main/java/com/chameleonvision/FileHelper.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.chameleonvision;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class FileHelper {
|
||||
private FileHelper() {} // no construction, utility class
|
||||
|
||||
public static void CheckPath(String path) {
|
||||
if (path.equals("")) return;
|
||||
Path realPath = Path.of(path);
|
||||
CheckPath(realPath);
|
||||
}
|
||||
|
||||
public static void CheckPath(Path path) {
|
||||
if (!Files.exists(path)) {
|
||||
try {
|
||||
Files.createDirectories(path);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Main/src/main/java/com/chameleonvision/Main.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.chameleonvision;
|
||||
|
||||
import com.chameleonvision.settings.SettingsManager;
|
||||
import com.chameleonvision.vision.camera.CameraManager;
|
||||
import com.chameleonvision.vision.process.VisionProcess;
|
||||
import com.chameleonvision.web.Server;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
if (CameraManager.initializeCameras()) {
|
||||
SettingsManager.initialize();
|
||||
for (var camSet : CameraManager.getAllCamerasByName().entrySet()) {
|
||||
new Thread(new VisionProcess(camSet.getValue())).start();
|
||||
}
|
||||
// NetworkTableInstance.getDefault().startClientTeam(SettingsManager.GeneralSettings.team_number);
|
||||
Server.main(8888);
|
||||
} else {
|
||||
System.err.println("No cameras connected!");
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Main/src/main/java/com/chameleonvision/MemoryManager.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package com.chameleonvision;
|
||||
|
||||
public class MemoryManager {
|
||||
|
||||
private static final long MEGABYTE_FACTOR = 1024L * 1024L;
|
||||
|
||||
private int collectionThreshold;
|
||||
private int lastUsedMb = 0;
|
||||
|
||||
public MemoryManager(int collectionThreshold) {
|
||||
this.collectionThreshold = collectionThreshold;
|
||||
}
|
||||
|
||||
public void setCollectionThreshold(int collectionThreshold) {
|
||||
this.collectionThreshold = collectionThreshold;
|
||||
}
|
||||
|
||||
public static long getUsedMemory() {
|
||||
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
}
|
||||
|
||||
public static int getUsedMemoryMB() {
|
||||
return (int) (getUsedMemory() / MEGABYTE_FACTOR);
|
||||
}
|
||||
|
||||
private static 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: %dMB\n", usedMem);
|
||||
}
|
||||
|
||||
if (usedMem >= collectionThreshold) {
|
||||
collect();
|
||||
if (print) System.out.printf("Garbage collected at %dMB\n", usedMem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.chameleonvision.settings;
|
||||
|
||||
import java.net.*;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
public class NetworkSettings {
|
||||
public String connectionType, ip, netmask, gateway, hostname;
|
||||
|
||||
public void run() {
|
||||
// String adapter = getAdapter();
|
||||
if (SystemUtils.IS_OS_LINUX) {//TODO check linux commands
|
||||
String adapter = getAdapter();
|
||||
if (!adapter.equals("")) {
|
||||
executeCommand("ifconfig " + adapter + " down");
|
||||
if (connectionType.equals("DHCP"))
|
||||
executeCommand("dhclient -r " + adapter);
|
||||
else if (connectionType.equals("Static")) {
|
||||
executeCommand("ifconfig " + adapter + " " + this.ip + " netmask " + this.netmask);
|
||||
executeCommand("route add default gw " + this.gateway + " " + adapter);
|
||||
}
|
||||
executeCommand("ifconfig " + adapter + " up");
|
||||
}
|
||||
executeCommand("hostnamectl set-hostname " + this.hostname);
|
||||
}
|
||||
// //TODO check windows commands
|
||||
// else if (SystemUtils.IS_OS_WINDOWS) {
|
||||
// if (!adapter.equals("")) {
|
||||
// if (connectionType.equals("DHCP")){
|
||||
// executeCommand("cmd /c interface ip set address \"" + adapter + "\" dhcp");
|
||||
// }
|
||||
// else if (connectionType.equals("Static")) {
|
||||
// executeCommand("cmd /c netsh interface ip set address \"" + adapter + "\" static " + this.ip + " " + this.netmask + " " + this.gateway + "1");
|
||||
// }
|
||||
// }
|
||||
// //TODO find a way to change hostname in windows
|
||||
// }
|
||||
}
|
||||
|
||||
private void executeCommand(String command) {
|
||||
try {
|
||||
Process p = Runtime.getRuntime().exec(command);
|
||||
System.out.println("Executing "+ command);
|
||||
p.waitFor();
|
||||
p.destroy();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error while executing command!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAdapter() {
|
||||
try {//TODO fix windows get adapter
|
||||
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
|
||||
for (NetworkInterface netint : Collections.list(nets)) {
|
||||
Enumeration<InetAddress> ee = netint.getInetAddresses();
|
||||
for (InetAddress addr : Collections.list(ee))
|
||||
if (addr instanceof Inet4Address)
|
||||
if ((addr.getAddress()[0] & 0xFF) == 192 && (addr.getAddress()[1] & 0xFF) == 168) {
|
||||
System.out.println("found robot network interface at " + netint.getName() + " ip: " + addr.getHostAddress());
|
||||
return netint.getName();
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
System.err.println("Socket exception while trying to find current ip");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.chameleonvision.settings;
|
||||
|
||||
import com.chameleonvision.FileHelper;
|
||||
import com.chameleonvision.vision.GeneralSettings;
|
||||
import com.chameleonvision.vision.camera.CameraManager;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class SettingsManager {
|
||||
public static final Path SettingsPath = Paths.get(System.getProperty("user.dir"), "Settings");
|
||||
public static com.chameleonvision.vision.GeneralSettings GeneralSettings;
|
||||
|
||||
private SettingsManager() {}
|
||||
|
||||
public static void initialize() {
|
||||
initGeneralSettings();
|
||||
NetworkSettings netSettings = new NetworkSettings();
|
||||
netSettings.hostname = GeneralSettings.hostname;
|
||||
netSettings.gateway = GeneralSettings.gateway;
|
||||
netSettings.netmask = GeneralSettings.netmask;
|
||||
netSettings.connectionType = GeneralSettings.connection_type;
|
||||
netSettings.ip = GeneralSettings.ip;
|
||||
netSettings.run();
|
||||
|
||||
var allCameras = CameraManager.getAllCamerasByName();
|
||||
if (!allCameras.containsKey(GeneralSettings.curr_camera) && allCameras.size() > 0) {
|
||||
var cam = allCameras.entrySet().stream().findFirst().get().getValue();
|
||||
GeneralSettings.curr_camera = cam.name;
|
||||
GeneralSettings.curr_pipeline = cam.getCurrentPipelineIndex();
|
||||
}
|
||||
}
|
||||
|
||||
public enum Platform {
|
||||
WINDOWS_64("Windows x64"),
|
||||
LINUX_64("Linux x64"),
|
||||
LINUX_RASPBIAN("Linux Raspbian"),
|
||||
LINUX_AARCH64("Linux ARM 64bit"),
|
||||
MACOS_64("Mac OS x64"),
|
||||
UNSUPPORTED("Unsupported Platform");
|
||||
|
||||
public final String value;
|
||||
|
||||
Platform(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static Platform getCurrentPlatform() {
|
||||
var osName = System.getProperty("os.name");
|
||||
var osArch = System.getProperty("os.arch");
|
||||
|
||||
if (osName.contains("Windows")) {
|
||||
if (osArch.equals("amd64")) return Platform.WINDOWS_64;
|
||||
return Platform.UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (osName.contains("Linux")) {
|
||||
if (osArch.equals("amd64")) return Platform.LINUX_64;
|
||||
if (osArch.contains("rasp")) return Platform.LINUX_RASPBIAN;
|
||||
if (osArch.contains("aarch")) return Platform.LINUX_64;
|
||||
return Platform.UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (osName.contains("Mac")) {
|
||||
if (osArch.equals("amd64")) return Platform.MACOS_64;
|
||||
return Platform.UNSUPPORTED;
|
||||
}
|
||||
|
||||
return Platform.UNSUPPORTED;
|
||||
}
|
||||
|
||||
private static void initGeneralSettings() {
|
||||
FileHelper.CheckPath(SettingsPath);
|
||||
try {
|
||||
GeneralSettings = new Gson().fromJson(new FileReader(Paths.get(SettingsPath.toString(), "Settings.json").toString()), com.chameleonvision.vision.GeneralSettings.class);
|
||||
} catch (FileNotFoundException e) {
|
||||
GeneralSettings = new GeneralSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateCameraSetting(String cameraName, int pipelineNumber) {
|
||||
GeneralSettings.curr_camera = cameraName;
|
||||
GeneralSettings.curr_pipeline = pipelineNumber;
|
||||
}
|
||||
|
||||
public static void updatePipelineSetting(int pipelineNumber) {
|
||||
GeneralSettings.curr_pipeline = pipelineNumber;
|
||||
}
|
||||
|
||||
public static void saveSettings() {
|
||||
CameraManager.saveCameras();
|
||||
saveGeneralSettings();
|
||||
}
|
||||
|
||||
private static void saveGeneralSettings() {
|
||||
try {
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
FileWriter writer = new FileWriter(Paths.get(SettingsPath.toString(), "settings.json").toString());
|
||||
gson.toJson(GeneralSettings, writer);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.chameleonvision.vision;
|
||||
|
||||
public class GeneralSettings {
|
||||
public int team_number = 1577;
|
||||
public String connection_type = "DHCP";
|
||||
public String ip = "";
|
||||
public String gateway = "";
|
||||
public String netmask = "";
|
||||
public String hostname = "Chameleon-vision";
|
||||
public String curr_camera = "";
|
||||
public Integer curr_pipeline = null;
|
||||
}
|
||||
25
Main/src/main/java/com/chameleonvision/vision/Pipeline.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.chameleonvision.vision;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Pipeline {
|
||||
public int exposure = 50;
|
||||
public int brightness = 50;
|
||||
public String orientation = "Normal";
|
||||
public List<Integer> hue = Arrays.asList(50, 180);
|
||||
public List<Integer> saturation = Arrays.asList(50, 255);
|
||||
public List<Integer> value = Arrays.asList(50, 255);
|
||||
public boolean erode = false;
|
||||
public boolean dilate = false;
|
||||
public List<Integer> area = Arrays.asList(0, 100);
|
||||
public List<Double> ratio = Arrays.asList(0D, 20D);
|
||||
public List<Integer> extent = Arrays.asList(0, 100);
|
||||
public int is_binary = 0;
|
||||
public String sort_mode = "Largest";
|
||||
public String target_group = "Single";
|
||||
public String target_intersection = "Up";
|
||||
public double M = 1;
|
||||
public double B = 0;
|
||||
public boolean is_calibrated = false;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.chameleonvision.vision.camera;
|
||||
|
||||
import edu.wpi.cscore.VideoMode;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class CamVideoMode {
|
||||
public final int fps;
|
||||
public final int width;
|
||||
public final int height;
|
||||
public final String pixel_format;
|
||||
|
||||
public CamVideoMode(VideoMode videoMode) {
|
||||
fps = videoMode.fps;
|
||||
width = videoMode.width;
|
||||
height = videoMode.height;
|
||||
pixel_format = videoMode.pixelFormat.name();
|
||||
}
|
||||
|
||||
public VideoMode.PixelFormat getActualPixelFormat() {
|
||||
return VideoMode.PixelFormat.valueOf(pixel_format);
|
||||
}
|
||||
|
||||
public boolean isEqualToVideoMode(VideoMode videoMode) {
|
||||
return videoMode.fps == fps && videoMode.width == width && videoMode.height == height && videoMode.pixelFormat == getActualPixelFormat();
|
||||
}
|
||||
|
||||
public boolean equals(VideoMode vm) {
|
||||
return vm.fps == fps &&
|
||||
vm.width == width &&
|
||||
vm.height == height &&
|
||||
vm.pixelFormat == getActualPixelFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj instanceof CamVideoMode) {
|
||||
var cvm = (CamVideoMode) obj;
|
||||
return cvm.fps == fps &&
|
||||
cvm.width == width &&
|
||||
cvm.height == height &&
|
||||
cvm.pixel_format.equals(pixel_format);
|
||||
} else if (obj instanceof VideoMode) {
|
||||
var vm = (VideoMode) obj;
|
||||
return equals(vm);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
199
Main/src/main/java/com/chameleonvision/vision/camera/Camera.java
Normal file
@@ -0,0 +1,199 @@
|
||||
package com.chameleonvision.vision.camera;
|
||||
|
||||
import com.chameleonvision.settings.SettingsManager;
|
||||
import com.chameleonvision.vision.Pipeline;
|
||||
import com.chameleonvision.web.ServerHandler;
|
||||
import edu.wpi.cscore.*;
|
||||
import edu.wpi.first.cameraserver.CameraServer;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class Camera {
|
||||
|
||||
private static final double DEFAULT_FOV = 60.8;
|
||||
private static final int MINIMUM_FPS = 30;
|
||||
private static final int MINIMUM_WIDTH = 320;
|
||||
private static final int MINIMUM_HEIGHT = 240;
|
||||
private static final int MAX_INIT_MS = 1500;
|
||||
|
||||
public final String name;
|
||||
public final String path;
|
||||
|
||||
private final UsbCamera UsbCam;
|
||||
private final VideoMode[] availableVideoModes;
|
||||
|
||||
private final CameraServer cs = CameraServer.getInstance();
|
||||
private final CvSink cvSink;
|
||||
private final Object cvSourceLock = new Object();
|
||||
private CvSource cvSource;
|
||||
private double FOV;
|
||||
private CameraValues camVals;
|
||||
private CamVideoMode camVideoMode;
|
||||
private int currentPipelineIndex;
|
||||
private HashMap<Integer, Pipeline> pipelines;
|
||||
private long initTimeout;
|
||||
|
||||
|
||||
public Camera(String cameraName) {
|
||||
this(cameraName, DEFAULT_FOV);
|
||||
}
|
||||
|
||||
public Camera(String cameraName, double fov) {
|
||||
this(cameraName,CameraManager.AllUsbCameraInfosByName.get(cameraName), fov);
|
||||
}
|
||||
|
||||
public Camera(String cameraName, UsbCameraInfo usbCamInfo, double fov) {
|
||||
this(cameraName ,usbCamInfo, fov, new HashMap<>(), 0);
|
||||
}
|
||||
|
||||
public Camera(String cameraName, double fov, int videoModeIndex) {
|
||||
this(cameraName, fov, new HashMap<>(), videoModeIndex);
|
||||
}
|
||||
|
||||
public Camera(String cameraName, double fov, HashMap<Integer, Pipeline> pipelines, int videoModeIndex) {
|
||||
this(cameraName, CameraManager.AllUsbCameraInfosByName.get(cameraName), fov, pipelines, videoModeIndex);
|
||||
}
|
||||
|
||||
public Camera(String cameraName, UsbCameraInfo usbCamInfo, double fov, HashMap<Integer, Pipeline> pipelines, int videoModeIndex) {
|
||||
FOV = fov;
|
||||
name = cameraName;
|
||||
path = usbCamInfo.path;
|
||||
|
||||
UsbCam = new UsbCamera(name, path);
|
||||
|
||||
this.pipelines = pipelines;
|
||||
|
||||
// set up video modes according to minimums
|
||||
if (SettingsManager.getCurrentPlatform() == SettingsManager.Platform.WINDOWS_64 && !UsbCam.isConnected()) {
|
||||
System.out.print("Waiting on camera... ");
|
||||
initTimeout = System.nanoTime();
|
||||
while(!UsbCam.isConnected())
|
||||
{
|
||||
//TODO add a time sleep, can wait only so long before giving up
|
||||
if (((System.nanoTime() - initTimeout) / 1e6 ) >= MAX_INIT_MS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
var initTimeMs = (System.nanoTime() - initTimeout) / 1e6;
|
||||
System.out.printf("Camera initialized in %.2fms\n", initTimeMs);
|
||||
}
|
||||
availableVideoModes = Arrays.stream(UsbCam.enumerateVideoModes()).filter(v -> v.fps >= MINIMUM_FPS && v.width >= MINIMUM_WIDTH && v.height >= MINIMUM_HEIGHT).toArray(VideoMode[]::new);
|
||||
if (videoModeIndex <= availableVideoModes.length - 1) {
|
||||
setCamVideoMode(videoModeIndex, false);
|
||||
} else {
|
||||
setCamVideoMode(0, false);
|
||||
}
|
||||
|
||||
cvSink = cs.getVideo(UsbCam);
|
||||
cvSource = cs.putVideo(name, camVals.ImageWidth, camVals.ImageHeight);
|
||||
var s = (MjpegServer) cs.getServer("serve_" + name);
|
||||
CameraManager.CameraPorts.put(name, s.getPort());
|
||||
}
|
||||
|
||||
VideoMode[] getAvailableVideoModes() {
|
||||
return availableVideoModes;
|
||||
}
|
||||
|
||||
public int getStreamPort() {
|
||||
var s = (MjpegServer) cs.getServer("serve_" + name);
|
||||
return s.getPort();
|
||||
}
|
||||
|
||||
public void setCamVideoMode(int videoMode, boolean updateCvSource) {
|
||||
setCamVideoMode(new CamVideoMode(availableVideoModes[videoMode]), updateCvSource);
|
||||
}
|
||||
|
||||
private void setCamVideoMode(CamVideoMode newVideoMode, boolean updateCvSource) {
|
||||
var prevVideoMode = this.camVideoMode;
|
||||
this.camVideoMode = newVideoMode;
|
||||
UsbCam.setPixelFormat(newVideoMode.getActualPixelFormat());
|
||||
UsbCam.setFPS(newVideoMode.fps);
|
||||
UsbCam.setResolution(newVideoMode.width, newVideoMode.height);
|
||||
|
||||
// update camera values
|
||||
camVals = new CameraValues(this);
|
||||
if (prevVideoMode != null && !prevVideoMode.equals(newVideoMode) && updateCvSource) { // if resolution changed
|
||||
synchronized (cvSourceLock) {
|
||||
cvSource = cs.putVideo(name, newVideoMode.width, newVideoMode.height);
|
||||
}
|
||||
ServerHandler.sendFullSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void addPipeline() {
|
||||
addPipeline(pipelines.size());
|
||||
}
|
||||
|
||||
private void addPipeline(int pipelineNumber) {
|
||||
if (pipelines.containsKey(pipelineNumber)) return;
|
||||
pipelines.put(pipelineNumber, new Pipeline());
|
||||
}
|
||||
|
||||
public Pipeline getCurrentPipeline() {
|
||||
return pipelines.get(currentPipelineIndex);
|
||||
}
|
||||
|
||||
public int getCurrentPipelineIndex() {
|
||||
return currentPipelineIndex;
|
||||
}
|
||||
|
||||
void setCurrentPipelineIndex(int pipelineNumber) {
|
||||
if (pipelineNumber - 1 > pipelines.size()) return;
|
||||
currentPipelineIndex = pipelineNumber;
|
||||
}
|
||||
|
||||
public HashMap<Integer, Pipeline> getPipelines() {
|
||||
return pipelines;
|
||||
}
|
||||
|
||||
public CamVideoMode getVideoMode() {
|
||||
return camVideoMode;
|
||||
}
|
||||
|
||||
public int getVideoModeIndex() {
|
||||
return IntStream.range(0, availableVideoModes.length)
|
||||
.filter(i -> camVideoMode.equals(availableVideoModes[i]))
|
||||
.findFirst()
|
||||
.orElse(-1);
|
||||
}
|
||||
|
||||
public double getFOV() {
|
||||
return FOV;
|
||||
}
|
||||
|
||||
public void setFOV(double fov) {
|
||||
FOV = fov;
|
||||
camVals = new CameraValues(this);
|
||||
}
|
||||
|
||||
public int getBrightness() {
|
||||
return getCurrentPipeline().brightness;
|
||||
}
|
||||
|
||||
public void setBrightness(int brightness) {
|
||||
getCurrentPipeline().brightness = brightness;
|
||||
UsbCam.setBrightness(brightness);
|
||||
}
|
||||
|
||||
public void setExposure(int exposure) {
|
||||
getCurrentPipeline().exposure = exposure;
|
||||
UsbCam.setExposureManual(exposure);
|
||||
}
|
||||
|
||||
public long grabFrame(Mat image) {
|
||||
return cvSink.grabFrame(image);
|
||||
}
|
||||
|
||||
public CameraValues getCamVals() {
|
||||
return camVals;
|
||||
}
|
||||
|
||||
public void putFrame(Mat image) {
|
||||
synchronized (cvSourceLock) {
|
||||
cvSource.putFrame(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.chameleonvision.vision.camera;
|
||||
|
||||
import com.chameleonvision.vision.Pipeline;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.type.MapType;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import com.google.gson.*;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class CameraDeserializer implements JsonDeserializer<Camera> {
|
||||
@Override
|
||||
public Camera deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
|
||||
var jsonObj = jsonElement.getAsJsonObject();
|
||||
var camFOV = jsonObj.get("FOV").getAsDouble();
|
||||
var camName = jsonObj.get("name").getAsString();
|
||||
var videoModeIndex = jsonObj.get("resolution").getAsInt();
|
||||
|
||||
|
||||
var pipelines = jsonObj.get("pipelines");
|
||||
HashMap<Integer, Pipeline> actualPipelines = new HashMap<>();
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
TypeFactory typeFactory = mapper.getTypeFactory();
|
||||
MapType mapType = typeFactory.constructMapType(HashMap.class, Integer.class, Pipeline.class);
|
||||
try {
|
||||
actualPipelines = mapper.readValue(pipelines.toString(), mapType);
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return actualPipelines != null ? new Camera(camName, camFOV, actualPipelines, videoModeIndex) : new Camera(camName, camFOV, videoModeIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.chameleonvision.vision.camera;
|
||||
|
||||
import com.chameleonvision.CameraException;
|
||||
import com.chameleonvision.FileHelper;
|
||||
import com.chameleonvision.settings.SettingsManager;
|
||||
import com.chameleonvision.vision.Pipeline;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import edu.wpi.cscore.UsbCamera;
|
||||
import edu.wpi.cscore.UsbCameraInfo;
|
||||
import org.opencv.videoio.VideoCapture;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class CameraManager {
|
||||
|
||||
private static final Path CamConfigPath = Paths.get(SettingsManager.SettingsPath.toString(), "Cams");
|
||||
public static HashMap<String, Integer> CameraPorts = new HashMap<>();
|
||||
|
||||
private static HashMap<String, Camera> AllCamerasByName = new HashMap<>();
|
||||
|
||||
static HashMap<String, UsbCameraInfo> AllUsbCameraInfosByName = new HashMap<>() {{
|
||||
var suffix = 0;
|
||||
for (var info : UsbCamera.enumerateUsbCameras()) {
|
||||
var cap = new VideoCapture(info.dev);
|
||||
if (cap.isOpened()) {
|
||||
cap.release();
|
||||
var name = info.name;
|
||||
while (this.containsKey(name)) {
|
||||
suffix++;
|
||||
name = String.format("%s(%s)", info.name, suffix);
|
||||
}
|
||||
put(name, info);
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
public static HashMap<String, Camera> getAllCamerasByName() {
|
||||
return AllCamerasByName;
|
||||
}
|
||||
|
||||
public static boolean initializeCameras() {
|
||||
if (AllUsbCameraInfosByName.size() == 0) return false;
|
||||
FileHelper.CheckPath(CamConfigPath);
|
||||
for (var entry : AllUsbCameraInfosByName.entrySet()) {
|
||||
var camPath = Paths.get(CamConfigPath.toString(), String.format("%s.json", entry.getKey()));
|
||||
File camJsonFile = new File(camPath.toString());
|
||||
if (camJsonFile.exists() && camJsonFile.length() != 0) {
|
||||
try {
|
||||
Gson gson = new GsonBuilder().registerTypeAdapter(Camera.class, new CameraDeserializer()).create();
|
||||
var camJsonFileReader = new FileReader(camPath.toString());
|
||||
var gsonRead = gson.fromJson(camJsonFileReader, Camera.class);
|
||||
AllCamerasByName.put(entry.getKey(), gsonRead);
|
||||
} catch (FileNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
if (!addCamera(new Camera(entry.getKey()), entry.getKey())) {
|
||||
System.err.println("Failed to add camera! Already exists!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean addCamera(Camera camera, String cameraName) {
|
||||
if (AllCamerasByName.containsKey(cameraName)) return false;
|
||||
camera.addPipeline();
|
||||
AllCamerasByName.put(cameraName, camera);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Camera getCamera(String cameraName) {
|
||||
return AllCamerasByName.get(cameraName);
|
||||
}
|
||||
|
||||
public static Camera getCurrentCamera() throws CameraException {
|
||||
if (AllCamerasByName.size() == 0) throw new CameraException(CameraException.CameraExceptionType.NO_CAMERA);
|
||||
var curCam = AllCamerasByName.get(SettingsManager.GeneralSettings.curr_camera);
|
||||
if (curCam == null) throw new CameraException(CameraException.CameraExceptionType.BAD_CAMERA);
|
||||
return curCam;
|
||||
}
|
||||
|
||||
public static void setCurrentCamera(String cameraName) throws CameraException {
|
||||
if (!AllCamerasByName.containsKey(cameraName))
|
||||
throw new CameraException(CameraException.CameraExceptionType.BAD_CAMERA);
|
||||
SettingsManager.GeneralSettings.curr_camera = cameraName;
|
||||
SettingsManager.updateCameraSetting(cameraName, getCurrentCamera().getCurrentPipelineIndex());
|
||||
}
|
||||
|
||||
public static Pipeline getCurrentPipeline() throws CameraException {
|
||||
return getCurrentCamera().getCurrentPipeline();
|
||||
}
|
||||
|
||||
public static void setCurrentPipeline(int pipelineNumber) throws CameraException {
|
||||
if (!getCurrentCamera().getPipelines().containsKey(pipelineNumber))
|
||||
throw new CameraException(CameraException.CameraExceptionType.BAD_PIPELINE);
|
||||
getCurrentCamera().setCurrentPipelineIndex(pipelineNumber);
|
||||
SettingsManager.updatePipelineSetting(pipelineNumber);
|
||||
}
|
||||
|
||||
public static List<String> getResolutionList() throws CameraException {
|
||||
if (!SettingsManager.GeneralSettings.curr_camera.equals("")) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (var res : CameraManager.getCamera(SettingsManager.GeneralSettings.curr_camera).getAvailableVideoModes()) {
|
||||
list.add(String.format("%s X %s at %s fps", res.width, res.height, res.fps));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
throw new CameraException(CameraException.CameraExceptionType.NO_CAMERA);
|
||||
}
|
||||
|
||||
public static void saveCameras() {
|
||||
for (var entry : AllCamerasByName.entrySet()) {
|
||||
try {
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Camera.class, new CameraSerializer()).create();
|
||||
FileWriter writer = new FileWriter(Paths.get(CamConfigPath.toString(), String.format("%s.json", entry.getKey())).toString());
|
||||
gson.toJson(entry.getValue(), writer);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.chameleonvision.vision.camera;
|
||||
import com.google.gson.*;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public class CameraSerializer implements JsonSerializer<Camera> {
|
||||
@Override
|
||||
public JsonElement serialize(Camera camera, Type type, JsonSerializationContext context) {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("FOV", camera.getFOV());
|
||||
obj.addProperty("path", camera.path);
|
||||
obj.addProperty("name", camera.name);
|
||||
|
||||
var pipelines = context.serialize(camera.getPipelines());
|
||||
obj.add("pipelines", pipelines);
|
||||
|
||||
obj.addProperty("resolution", camera.getVideoModeIndex());
|
||||
obj.add("camVideoMode", context.serialize(camera.getVideoMode()));
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.chameleonvision.vision.camera;
|
||||
|
||||
import org.apache.commons.math3.fraction.Fraction;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class CameraValues {
|
||||
public final int ImageWidth;
|
||||
public final int ImageHeight;
|
||||
public final double FOV;
|
||||
public final double ImageArea;
|
||||
public final double CenterX;
|
||||
public final double CenterY;
|
||||
public final double DiagonalView;
|
||||
public final Fraction AspectFraction;
|
||||
public final int HorizontalRatio;
|
||||
public final int VerticalRatio;
|
||||
public final double HorizontalView;
|
||||
public final double VerticalView;
|
||||
public final double HorizontalFocalLength;
|
||||
public final double VerticalFocalLength;
|
||||
|
||||
public CameraValues(Camera camera) {
|
||||
this(camera.getVideoMode().width, camera.getVideoMode().height, camera.getFOV());
|
||||
}
|
||||
|
||||
public CameraValues(int imageWidth, int imageHeight, double fov) {
|
||||
ImageWidth = imageWidth;
|
||||
ImageHeight = imageHeight;
|
||||
FOV = fov;
|
||||
ImageArea = ImageWidth * ImageHeight;
|
||||
CenterX = ((double) ImageWidth / 2) - 0.5;
|
||||
CenterY = ((double) ImageHeight / 2) - 0.5;
|
||||
DiagonalView = FastMath.toRadians(FOV);
|
||||
AspectFraction = new Fraction(ImageWidth, ImageHeight);
|
||||
HorizontalRatio = AspectFraction.getNumerator();
|
||||
VerticalRatio = AspectFraction.getDenominator();
|
||||
HorizontalView = FastMath.atan(FastMath.tan(DiagonalView / 2) * (HorizontalRatio / DiagonalView)) * 2;
|
||||
VerticalView = FastMath.atan(FastMath.tan(DiagonalView/2) * (VerticalRatio / DiagonalView)) * 2;
|
||||
HorizontalFocalLength = ImageWidth / (2 * FastMath.tan(HorizontalView /2));
|
||||
VerticalFocalLength = ImageHeight / (2 * FastMath.tan(VerticalView /2));
|
||||
}
|
||||
public double CalculatePitch(double PixelY, double centerY){
|
||||
double pitch = FastMath.toDegrees(FastMath.atan((PixelY - centerY) / VerticalFocalLength));
|
||||
return (pitch * -1);
|
||||
}
|
||||
public double CalculateYaw(double PixelX, double centerX){
|
||||
return FastMath.toDegrees(FastMath.atan((PixelX - centerX) / HorizontalFocalLength));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package com.chameleonvision.vision.process;
|
||||
|
||||
import com.chameleonvision.vision.camera.CameraValues;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.opencv.core.*;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class CVProcess {
|
||||
|
||||
private final CameraValues CamVals;
|
||||
private HashMap<String, Integer> TargetGrouping = new HashMap<>() {{
|
||||
put("Single", 1);
|
||||
put("Dual", 2);
|
||||
put("Triple", 3);
|
||||
put("Quadruple", 4);
|
||||
put("Quintuple", 5);
|
||||
}};
|
||||
private Mat Kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
|
||||
private Mat hsvImage = new Mat();
|
||||
private List<MatOfPoint> FoundContours = new ArrayList<>();
|
||||
private Mat binaryMat = new Mat();
|
||||
private List<MatOfPoint> FilteredContours = new ArrayList<>();
|
||||
private Comparator<RotatedRect> SortByCentermostComparator = Comparator.comparingDouble(this::calcDistance);
|
||||
private List<RotatedRect> FinalCountours = new ArrayList<>();
|
||||
private Mat intersectMatA = new Mat();
|
||||
private Mat intersectMatB = new Mat();
|
||||
|
||||
CVProcess(CameraValues camVals) {
|
||||
CamVals = camVals;
|
||||
}
|
||||
|
||||
void HSVThreshold(Mat srcImage, Mat dst, @NotNull Scalar hsvLower, @NotNull Scalar hsvUpper, boolean shouldErode, boolean shouldDilate) {
|
||||
Imgproc.cvtColor(srcImage, hsvImage, Imgproc.COLOR_RGB2HSV, 3);
|
||||
Core.inRange(hsvImage, hsvLower, hsvUpper, dst);
|
||||
if (shouldErode) {
|
||||
Imgproc.erode(dst, dst, Kernel);
|
||||
}
|
||||
if (shouldDilate) {
|
||||
Imgproc.dilate(dst, dst, Kernel);
|
||||
}
|
||||
hsvImage.release();
|
||||
}
|
||||
|
||||
List<MatOfPoint> FindContours(Mat src) {
|
||||
src.copyTo(binaryMat);
|
||||
FoundContours.clear();
|
||||
Imgproc.findContours(binaryMat, FoundContours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_TC89_L1);
|
||||
binaryMat.release();
|
||||
return FoundContours;
|
||||
}
|
||||
|
||||
List<MatOfPoint> FilterContours(List<MatOfPoint> InputContours, List<Integer> area, List<Double> ratio, List<Integer> extent) {
|
||||
for (MatOfPoint Contour : InputContours) {
|
||||
try {
|
||||
double contourArea = Imgproc.contourArea(Contour); //TODO change scaling
|
||||
double targetArea = (contourArea / CamVals.ImageArea) * 100;
|
||||
double minArea = Math.pow(area.get(0), 4);
|
||||
double maxArea = Math.pow(area.get(1), 4);
|
||||
if (targetArea < minArea || targetArea > maxArea) {
|
||||
continue;
|
||||
}
|
||||
var rect = Imgproc.minAreaRect(new MatOfPoint2f(Contour.toArray()));
|
||||
var targetFullness = (contourArea / rect.size.area()) * 100;
|
||||
if (targetFullness < extent.get(0) || targetArea > extent.get(1)) {
|
||||
continue;
|
||||
}
|
||||
double aspectRatio = rect.size.width / rect.size.height;//TODO i think aspectRatio is inverted
|
||||
if (aspectRatio < ratio.get(0) || aspectRatio > ratio.get(1)) {
|
||||
continue;
|
||||
}
|
||||
FilteredContours.add(Contour);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error while filtering contours");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return FilteredContours;
|
||||
}
|
||||
|
||||
private double calcDistance(RotatedRect rect) {
|
||||
return FastMath.sqrt(FastMath.pow(CamVals.CenterX - rect.center.x, 2) + FastMath.pow(CamVals.CenterY - rect.center.y, 2));
|
||||
}
|
||||
|
||||
RotatedRect SortTargetsToOne(List<RotatedRect> inputRects, String sortMode) {
|
||||
switch (sortMode) {
|
||||
case "Largest":
|
||||
return Collections.max(inputRects, Comparator.comparing(rect -> rect.size.area()));
|
||||
case "Smallest":
|
||||
return Collections.min(inputRects, Comparator.comparing(rect -> rect.size.area()));
|
||||
case "Highest":
|
||||
return Collections.min(inputRects, Comparator.comparing(rect -> rect.center.y));
|
||||
case "Lowest":
|
||||
return Collections.max(inputRects, Comparator.comparing(rect -> rect.center.y));
|
||||
case "Leftmost":
|
||||
return Collections.min(inputRects, Comparator.comparing(rect -> rect.center.x));
|
||||
case "Rightmost":
|
||||
return Collections.max(inputRects, Comparator.comparing(rect -> rect.center.x));
|
||||
case "Centermost":
|
||||
return Collections.min(inputRects, SortByCentermostComparator);
|
||||
default:
|
||||
return inputRects.get(0); // default to whatever the first contour is, but this should never happen
|
||||
}
|
||||
}
|
||||
|
||||
List<RotatedRect> GroupTargets(List<MatOfPoint> InputContours, String IntersectionPoint, String TargetGroup) {
|
||||
FinalCountours.clear();
|
||||
if (!TargetGroup.equals("Single")) {
|
||||
for (var i = 0; i < InputContours.size(); i++) {
|
||||
List<Point> FinalContourList = new ArrayList<>(InputContours.get(i).toList());
|
||||
for (var c = 0; c < (TargetGrouping.get(TargetGroup) - 1); c++) {
|
||||
try {
|
||||
MatOfPoint firstContour = InputContours.get(i + c);
|
||||
MatOfPoint secondContour = InputContours.get(i + c + 1);
|
||||
if (IsIntersecting(firstContour, secondContour, IntersectionPoint)) {
|
||||
FinalContourList.addAll(secondContour.toList());
|
||||
}
|
||||
else{
|
||||
FinalContourList.clear();
|
||||
break;
|
||||
}
|
||||
firstContour.release();
|
||||
secondContour.release();
|
||||
MatOfPoint2f contour = new MatOfPoint2f();
|
||||
contour.fromList(FinalContourList);
|
||||
if (contour.cols() != 0 && contour.rows() != 0) {
|
||||
RotatedRect rect = Imgproc.minAreaRect(contour);
|
||||
FinalCountours.add(rect);
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
FinalContourList.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
for (MatOfPoint inputContour : InputContours) {
|
||||
MatOfPoint2f contour = new MatOfPoint2f();
|
||||
contour.fromArray(inputContour.toArray());
|
||||
if (contour.cols() != 0 && contour.rows() != 0) {
|
||||
RotatedRect rect = Imgproc.minAreaRect(contour);
|
||||
FinalCountours.add(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
return FinalCountours;
|
||||
}
|
||||
|
||||
private boolean IsIntersecting(MatOfPoint ContourOne, MatOfPoint ContourTwo, String IntersectionPoint) {
|
||||
if (IntersectionPoint.equals("None")) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
Imgproc.fitLine(ContourOne, intersectMatA, Imgproc.CV_DIST_L2, 0, 0.01, 0.01);
|
||||
Imgproc.fitLine(ContourTwo, intersectMatB, Imgproc.CV_DIST_L2, 0, 0.01, 0.01);
|
||||
double vxA = intersectMatA.get(0, 0)[0];
|
||||
double vyA = intersectMatA.get(1, 0)[0];
|
||||
double x0A = intersectMatA.get(2, 0)[0];
|
||||
double y0A = intersectMatA.get(3, 0)[0];
|
||||
double mA = vyA / vxA;
|
||||
double vxB = intersectMatB.get(0, 0)[0];
|
||||
double vyB = intersectMatB.get(1, 0)[0];
|
||||
double x0B = intersectMatB.get(2, 0)[0];
|
||||
double y0B = intersectMatB.get(3, 0)[0];
|
||||
double mB = vyB / vxB;
|
||||
double bA = y0A - (mA*x0A);
|
||||
double bB = y0B - (mB*x0B);
|
||||
double intersectionX = ((mA * x0A) - y0A - (mB * x0B) + y0B )/ (mA - mB);
|
||||
double intersectionY = (mA * (intersectionX - x0A)) + y0A;
|
||||
double massX = intersectionX + 1;
|
||||
double massY = intersectionY + ((mA + bA + mB +bB) / 2);
|
||||
switch (IntersectionPoint) {
|
||||
case "Up": {
|
||||
if (intersectionY < massY) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Down": {
|
||||
if (intersectionY > massY) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Left": {
|
||||
if (intersectionX < massX) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Right": {
|
||||
if (intersectionX > massX) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.chameleonvision.vision.process;
|
||||
|
||||
import com.chameleonvision.vision.camera.Camera;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
public class CameraProcess implements Runnable {
|
||||
|
||||
private final Camera camera;
|
||||
private final int maxFPS;
|
||||
private final Object inputFrameLock = new Object();
|
||||
private final Object outputFrameLock = new Object();
|
||||
private Mat inputFrame;
|
||||
private Mat outputFrame;
|
||||
private long timestamp;
|
||||
|
||||
CameraProcess(Camera camera) {
|
||||
this.camera = camera;
|
||||
maxFPS = camera.getVideoMode().fps;
|
||||
var camVals = camera.getCamVals();
|
||||
inputFrame = new Mat(camVals.ImageWidth, camVals.ImageHeight, CvType.CV_8UC3);
|
||||
outputFrame = new Mat(camVals.ImageWidth, camVals.ImageHeight, CvType.CV_8UC3);
|
||||
}
|
||||
|
||||
private void updateFrameSize() {
|
||||
var camVals = camera.getCamVals();
|
||||
synchronized (inputFrameLock) {
|
||||
inputFrame = new Mat(camVals.ImageWidth, camVals.ImageHeight, CvType.CV_8UC3);
|
||||
}
|
||||
synchronized (outputFrameLock) {
|
||||
outputFrame = new Mat(camVals.ImageWidth, camVals.ImageHeight, CvType.CV_8UC3);
|
||||
}
|
||||
}
|
||||
|
||||
void updateFrame(Mat inputFrame) {
|
||||
synchronized (inputFrameLock) {
|
||||
inputFrame.copyTo(this.inputFrame);
|
||||
}
|
||||
}
|
||||
|
||||
long getLatestFrame(Mat outputFrame) {
|
||||
synchronized (outputFrameLock) {
|
||||
this.outputFrame.copyTo(outputFrame);
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!Thread.interrupted()) {
|
||||
synchronized (outputFrameLock) {
|
||||
timestamp = camera.grabFrame(outputFrame);
|
||||
}
|
||||
synchronized (inputFrameLock) {
|
||||
camera.putFrame(inputFrame);
|
||||
}
|
||||
var msToWait = (long) 1000 / maxFPS;
|
||||
try {
|
||||
Thread.sleep(msToWait);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.chameleonvision.vision.process;
|
||||
|
||||
import org.opencv.core.RotatedRect;
|
||||
|
||||
public class PipelineResult {
|
||||
public boolean IsValid = false;
|
||||
public double CalibratedX = 0.0;
|
||||
public double CalibratedY = 0.0;
|
||||
public double Pitch = 0.0;
|
||||
public double Yaw = 0.0;
|
||||
RotatedRect RawPoint;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.chameleonvision.vision.process;
|
||||
|
||||
import com.chameleonvision.settings.SettingsManager;
|
||||
import com.chameleonvision.vision.Pipeline;
|
||||
import com.chameleonvision.vision.camera.Camera;
|
||||
import com.chameleonvision.web.ServerHandler;
|
||||
import edu.wpi.first.networktables.*;
|
||||
import org.opencv.core.*;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class VisionProcess implements Runnable {
|
||||
|
||||
private final Camera camera;
|
||||
private final String cameraName;
|
||||
private final CameraProcess cameraProcess;
|
||||
// NetworkTables
|
||||
private NetworkTableEntry ntPipelineEntry;
|
||||
private NetworkTableEntry ntDriverModeEntry;
|
||||
private NetworkTableEntry ntYawEntry;
|
||||
private NetworkTableEntry ntPitchEntry;
|
||||
private NetworkTableEntry ntDistanceEntry;
|
||||
private NetworkTableEntry ntTimeStampEntry;
|
||||
private NetworkTableEntry ntValidEntry;
|
||||
// chameleon specific
|
||||
private Pipeline currentPipeline;
|
||||
private CVProcess cvProcess;
|
||||
// pipeline process items
|
||||
private List<MatOfPoint> FoundContours = new ArrayList<>();
|
||||
private List<MatOfPoint> FilteredContours = new ArrayList<>();
|
||||
private List<RotatedRect> GroupedContours = new ArrayList<>();
|
||||
private Mat cameraInputMat = new Mat();
|
||||
private Mat hsvThreshMat = new Mat();
|
||||
private Mat streamOutputMat = new Mat();
|
||||
private Scalar contourRectColor = new Scalar(255, 0, 0);
|
||||
private long TimeStamp = 0;
|
||||
|
||||
public VisionProcess(Camera processCam) {
|
||||
camera = processCam;
|
||||
this.cameraName = camera.name;
|
||||
|
||||
// NetworkTables
|
||||
NetworkTable ntTable = NetworkTableInstance.getDefault().getTable("/chameleon-vision/" + cameraName);
|
||||
ntPipelineEntry = ntTable.getEntry("Pipeline");
|
||||
ntDriverModeEntry = ntTable.getEntry("Driver_Mode");
|
||||
ntPitchEntry = ntTable.getEntry("Pitch");
|
||||
ntYawEntry = ntTable.getEntry("Yaw");
|
||||
ntDistanceEntry = ntTable.getEntry("Distance");
|
||||
ntTimeStampEntry = ntTable.getEntry("TimeStamp");
|
||||
ntValidEntry = ntTable.getEntry("Valid");
|
||||
ntDriverModeEntry.addListener(this::DriverModeListener, EntryListenerFlags.kUpdate);
|
||||
ntPipelineEntry.addListener(this::PipelineListener, EntryListenerFlags.kUpdate);
|
||||
ntDriverModeEntry.setBoolean(false);
|
||||
ntPipelineEntry.setString("pipeline" + camera.getCurrentPipelineIndex());
|
||||
|
||||
// camera settings
|
||||
cvProcess = new CVProcess(camera.getCamVals());
|
||||
cameraProcess = new CameraProcess(camera);
|
||||
}
|
||||
|
||||
private void DriverModeListener(EntryNotification entryNotification) {
|
||||
if (entryNotification.value.getBoolean()) {
|
||||
camera.setExposure(25);
|
||||
camera.setBrightness(15);
|
||||
} else {
|
||||
Pipeline pipeline = camera.getCurrentPipeline();
|
||||
camera.setExposure(pipeline.exposure);
|
||||
camera.setBrightness(pipeline.brightness);
|
||||
}
|
||||
}
|
||||
|
||||
private void PipelineListener(EntryNotification entryNotification) {
|
||||
var ntPipelineIndex = Integer.parseInt(entryNotification.value.getString().replace("pipeline", ""));
|
||||
if (camera.getPipelines().containsKey(ntPipelineIndex)) {
|
||||
// camera.setEntryNotification.value.getString());
|
||||
var pipeline = camera.getCurrentPipeline();
|
||||
|
||||
camera.setExposure(pipeline.exposure);
|
||||
camera.setBrightness(pipeline.brightness);
|
||||
HashMap<String, Object> pipeChange = new HashMap<>();
|
||||
pipeChange.put("curr_pipeline", ntPipelineIndex);
|
||||
ServerHandler.broadcastMessage(pipeChange);
|
||||
|
||||
} else {
|
||||
ntPipelineEntry.setString("pipeline" + camera.getCurrentPipelineIndex());
|
||||
}
|
||||
}
|
||||
|
||||
private void drawContour(Mat inputMat, RotatedRect contourRect) {
|
||||
if (contourRect == null) return;
|
||||
List<MatOfPoint> drawnContour = new ArrayList<>();
|
||||
Point[] vertices = new Point[4];
|
||||
contourRect.points(vertices);
|
||||
drawnContour.add(new MatOfPoint(vertices));
|
||||
Imgproc.drawContours(inputMat, drawnContour, 0, contourRectColor, 3);
|
||||
Imgproc.circle(inputMat, contourRect.center, 3, contourRectColor);
|
||||
}
|
||||
|
||||
private void updateNetworkTables(PipelineResult pipelineResult) {
|
||||
ntValidEntry.setBoolean(pipelineResult.IsValid);
|
||||
if (pipelineResult.IsValid) {
|
||||
ntYawEntry.setNumber(pipelineResult.Yaw);
|
||||
ntPitchEntry.setNumber(pipelineResult.Pitch);
|
||||
}
|
||||
ntTimeStampEntry.setNumber(TimeStamp);
|
||||
}
|
||||
|
||||
private PipelineResult runVisionProcess(Mat inputImage, Mat outputImage) {
|
||||
var pipelineResult = new PipelineResult();
|
||||
|
||||
if (currentPipeline == null) {
|
||||
return pipelineResult;
|
||||
}
|
||||
if (!currentPipeline.orientation.equals("Normal")) {
|
||||
Core.flip(inputImage, inputImage, -1);
|
||||
}
|
||||
if (ntDriverModeEntry.getBoolean(false)) {
|
||||
inputImage.copyTo(outputImage);
|
||||
return pipelineResult;
|
||||
}
|
||||
Scalar hsvLower = new Scalar(currentPipeline.hue.get(0), currentPipeline.saturation.get(0), currentPipeline.value.get(0));
|
||||
Scalar hsvUpper = new Scalar(currentPipeline.hue.get(1), currentPipeline.saturation.get(1), currentPipeline.value.get(1));
|
||||
|
||||
cvProcess.HSVThreshold(inputImage, hsvThreshMat, hsvLower, hsvUpper, currentPipeline.erode, currentPipeline.dilate);
|
||||
|
||||
if (currentPipeline.is_binary == 1) {
|
||||
Imgproc.cvtColor(hsvThreshMat, outputImage, Imgproc.COLOR_GRAY2BGR, 3);
|
||||
} else {
|
||||
inputImage.copyTo(outputImage);
|
||||
}
|
||||
FoundContours = cvProcess.FindContours(hsvThreshMat);
|
||||
if (FoundContours.size() > 0) {
|
||||
FilteredContours = cvProcess.FilterContours(FoundContours, currentPipeline.area, currentPipeline.ratio, currentPipeline.extent);
|
||||
if (FilteredContours.size() > 0) {
|
||||
GroupedContours = cvProcess.GroupTargets(FilteredContours, currentPipeline.target_intersection, currentPipeline.target_group);
|
||||
if (GroupedContours.size() > 0) {
|
||||
var finalRect = cvProcess.SortTargetsToOne(GroupedContours, currentPipeline.sort_mode);
|
||||
pipelineResult.RawPoint = finalRect;
|
||||
pipelineResult.IsValid = true;
|
||||
if (!currentPipeline.is_calibrated) {
|
||||
pipelineResult.CalibratedX = camera.getCamVals().CenterX;
|
||||
pipelineResult.CalibratedY = camera.getCamVals().CenterY;
|
||||
} else {
|
||||
pipelineResult.CalibratedX = (finalRect.center.y - currentPipeline.B) / currentPipeline.M;
|
||||
pipelineResult.CalibratedY = finalRect.center.x * currentPipeline.M + currentPipeline.B;
|
||||
}
|
||||
pipelineResult.Pitch = camera.getCamVals().CalculatePitch(finalRect.center.y, pipelineResult.CalibratedY);
|
||||
pipelineResult.Yaw = camera.getCamVals().CalculateYaw(finalRect.center.x, pipelineResult.CalibratedX);
|
||||
drawContour(outputImage, finalRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pipelineResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// processing time tracking
|
||||
long startTime;
|
||||
long fpsLastTime = 0;
|
||||
double processTimeMs;
|
||||
double fps = 0;
|
||||
double uiFps = 0;
|
||||
int maxFps = camera.getVideoMode().fps;
|
||||
|
||||
new Thread(cameraProcess).start();
|
||||
|
||||
long lastFrameEndNanosec = 0;
|
||||
|
||||
while (!Thread.interrupted()) {
|
||||
startTime = System.nanoTime();
|
||||
if ((startTime - lastFrameEndNanosec) * 1e-6 >= 1000.0 / maxFps + 3) { // 3 additional fps to allow for overhead
|
||||
FoundContours.clear();
|
||||
FilteredContours.clear();
|
||||
GroupedContours.clear();
|
||||
|
||||
// update FPS for ui only every 0.5 seconds
|
||||
if ((startTime - fpsLastTime) * 1e-6 >= 500) {
|
||||
if (fps >= maxFps) {
|
||||
uiFps = maxFps;
|
||||
} else {
|
||||
uiFps = fps;
|
||||
}
|
||||
fpsLastTime = System.nanoTime();
|
||||
}
|
||||
|
||||
currentPipeline = camera.getCurrentPipeline();
|
||||
// start fps counter right before grabbing input frame
|
||||
TimeStamp = cameraProcess.getLatestFrame(cameraInputMat);
|
||||
if (cameraInputMat.cols() == 0 && cameraInputMat.rows() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get vision data
|
||||
var pipelineResult = runVisionProcess(cameraInputMat, streamOutputMat);
|
||||
updateNetworkTables(pipelineResult);
|
||||
if (cameraName.equals(SettingsManager.GeneralSettings.curr_camera)) {
|
||||
HashMap<String, Object> WebSend = new HashMap<>();
|
||||
HashMap<String, Object> point = new HashMap<>();
|
||||
List<Double> center = new ArrayList<>();
|
||||
if (pipelineResult.IsValid) {
|
||||
center.add(pipelineResult.RawPoint.center.x);
|
||||
center.add(pipelineResult.RawPoint.center.y);
|
||||
point.put("pitch", pipelineResult.Pitch);
|
||||
point.put("yaw", pipelineResult.Yaw);
|
||||
} else {
|
||||
center.add(0.0);
|
||||
center.add(0.0);
|
||||
point.put("pitch", 0);
|
||||
point.put("yaw", 0);
|
||||
}
|
||||
point.put("fps", uiFps);
|
||||
WebSend.put("point", point);
|
||||
WebSend.put("raw_point", center);
|
||||
ServerHandler.broadcastMessage(WebSend);
|
||||
}
|
||||
|
||||
cameraProcess.updateFrame(streamOutputMat);
|
||||
|
||||
cameraInputMat.release();
|
||||
hsvThreshMat.release();
|
||||
|
||||
// calculate FPS
|
||||
lastFrameEndNanosec = System.nanoTime();
|
||||
processTimeMs = (lastFrameEndNanosec - startTime) * 1e-6;
|
||||
fps = 1000 / processTimeMs;
|
||||
//please dont enable if you are not debugging
|
||||
// System.out.printf("%s - Process time: %-5.2fms, FPS: %-5.2f, FoundContours: %d, FilteredContours: %d, GroupedContours: %d\n", cameraName, processTimeMs, fps, FoundContours.size(), FilteredContours.size(), GroupedContours.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Main/src/main/java/com/chameleonvision/web/Server.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.chameleonvision.web;
|
||||
|
||||
import com.chameleonvision.settings.SettingsManager;
|
||||
import io.javalin.Javalin;
|
||||
|
||||
|
||||
public class Server {
|
||||
public static ServerHandler handler;
|
||||
|
||||
public static void main(int port) {
|
||||
handler = new ServerHandler();
|
||||
|
||||
Javalin app = Javalin.create();
|
||||
app.config.addStaticFiles("web");
|
||||
app.ws("/websocket", ws -> {
|
||||
ws.onConnect(ctx -> {
|
||||
handler.onConnect(ctx);
|
||||
System.out.println("Socket Connected");
|
||||
});
|
||||
ws.onClose(ctx -> {
|
||||
handler.onClose(ctx);
|
||||
System.out.println("Socket Disconnected");
|
||||
SettingsManager.saveSettings();
|
||||
});
|
||||
ws.onMessage(ctx -> {
|
||||
handler.onMessage(ctx);
|
||||
});
|
||||
});
|
||||
app.start(port);
|
||||
}
|
||||
}
|
||||
204
Main/src/main/java/com/chameleonvision/web/ServerHandler.java
Normal file
@@ -0,0 +1,204 @@
|
||||
package com.chameleonvision.web;
|
||||
|
||||
import com.chameleonvision.CameraException;
|
||||
import com.chameleonvision.settings.SettingsManager;
|
||||
import com.chameleonvision.vision.Pipeline;
|
||||
import com.chameleonvision.vision.camera.Camera;
|
||||
import com.chameleonvision.vision.camera.CameraManager;
|
||||
import com.google.gson.JsonArray;
|
||||
import edu.wpi.cscore.VideoException;
|
||||
import io.javalin.websocket.WsCloseContext;
|
||||
import io.javalin.websocket.WsConnectContext;
|
||||
import io.javalin.websocket.WsContext;
|
||||
import io.javalin.websocket.WsMessageContext;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ServerHandler {
|
||||
|
||||
private static List<WsContext> users;
|
||||
|
||||
ServerHandler() {
|
||||
users = new ArrayList<>();
|
||||
}
|
||||
|
||||
void onConnect(WsConnectContext context) {
|
||||
users.add(context);
|
||||
sendFullSettings();
|
||||
}
|
||||
|
||||
public void onClose(WsCloseContext context) {
|
||||
users.remove(context);
|
||||
}
|
||||
|
||||
void onMessage(WsMessageContext data) throws CameraException {
|
||||
broadcastMessage(data.message(), data);
|
||||
|
||||
JSONObject jsonObject = new JSONObject(data.message());
|
||||
String key = null;
|
||||
var jsonKeySetArray = jsonObject.keySet().toArray();
|
||||
try {
|
||||
key = jsonKeySetArray[0].toString();
|
||||
} catch (Exception ex) {
|
||||
System.err.println("WebSocket JSON data was empty!");
|
||||
}
|
||||
if (key == null) return;
|
||||
Object value = jsonObject.get(key);
|
||||
System.out.printf("Got websocket json data: [%s, %s]\n", key, value);
|
||||
if (hasField(CameraManager.getCurrentPipeline(), key)) {
|
||||
//Special cases for exposure and brightness and aspect ratio
|
||||
switch (key) {
|
||||
case "exposure":
|
||||
int newExposure = (int) value;
|
||||
System.out.printf("Changing exposure to %d\n", newExposure);
|
||||
try {
|
||||
CameraManager.getCurrentCamera().setExposure(newExposure);
|
||||
} catch (VideoException e) {
|
||||
System.out.println("Exposure changes is not supported on your webcam/webcam's driver");
|
||||
}
|
||||
break;
|
||||
case "brightness":
|
||||
int newBrightness = (int) value;
|
||||
System.out.printf("Changing brightness to %d\n", newBrightness);
|
||||
CameraManager.getCurrentCamera().setBrightness(newBrightness);
|
||||
break;
|
||||
case "ratio":
|
||||
//If there is any better to convert Integer to Double you're welcome to change it
|
||||
List<Double> doubleRatio = CameraManager.getCurrentPipeline().ratio;
|
||||
List<Object> newRatio = ((JSONArray) value).toList();
|
||||
for (int i = 0; i < newRatio.size(); i++) {
|
||||
doubleRatio.set(i, Double.parseDouble(newRatio.get(i).toString()));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//Any other field in CameraManager that doesn't need anything special
|
||||
setField(CameraManager.getCurrentPipeline(), key, value);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (key) {
|
||||
case "change_general_settings_values":
|
||||
JSONObject newSettings = (JSONObject) value;
|
||||
Map<String, Object> map = newSettings.toMap();
|
||||
map.forEach((s, o) -> setField(SettingsManager.GeneralSettings, s, o));
|
||||
SettingsManager.saveSettings();
|
||||
break;
|
||||
case "curr_camera":
|
||||
String newCamera = (String) value;
|
||||
System.out.printf("Changing camera to %s\n", newCamera);
|
||||
CameraManager.setCurrentCamera(newCamera);
|
||||
HashMap<String, Integer> portMap = new HashMap<>();
|
||||
portMap.put("port", CameraManager.getCurrentCamera().getStreamPort());
|
||||
sendFullSettings();
|
||||
break;
|
||||
case "curr_pipeline":
|
||||
String newPipeline = (String) value;
|
||||
var pipelineNumber = Integer.parseInt(newPipeline.replace("pipeline", ""));
|
||||
System.out.printf("Changing pipeline to %s\n", newPipeline);
|
||||
CameraManager.setCurrentPipeline(pipelineNumber);
|
||||
// broadcastMessage(allFieldsToMap(CameraManager.getCurrentPipeline()));
|
||||
broadcastMessage(allFieldsToMap(CameraManager.getCurrentPipeline()));
|
||||
break;
|
||||
case "resolution":
|
||||
int newVideoMode = (int) value;
|
||||
System.out.printf("Changing video mode to %d\n", newVideoMode);
|
||||
CameraManager.getCurrentCamera().setCamVideoMode(newVideoMode, true);
|
||||
break;
|
||||
case "FOV":
|
||||
double newFov = Double.parseDouble(value.toString());
|
||||
System.out.printf("Changing FOV to %f\n", newFov);
|
||||
CameraManager.getCurrentCamera().setFOV(newFov);
|
||||
break;
|
||||
default:
|
||||
System.out.printf("Unexpected value from websocket: [%s, %s]\n", key, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setField(Object obj, String fieldName, Object value) {
|
||||
try {
|
||||
Field[] fields = obj.getClass().getFields();
|
||||
for (Field f : fields) {
|
||||
if (f.getName().equals(fieldName)) {
|
||||
if (BeanUtils.isSimpleValueType(value.getClass())) {
|
||||
f.set(obj, value);
|
||||
} else if (value.getClass() == JSONArray.class) {
|
||||
f.set(obj, ((JSONArray) value).toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
System.out.println("IllegalAccessException ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void broadcastMessage(Object obj, WsContext userToSkip) {//TODO check if session id is a good way to differentiate users
|
||||
for (var user : users) {
|
||||
if (userToSkip != null && user.getSessionId().equals(userToSkip.getSessionId())) {
|
||||
continue;
|
||||
}
|
||||
if (obj.getClass() == String.class)
|
||||
user.send((String) obj);
|
||||
else if (obj.getClass() == HashMap.class)
|
||||
user.send(new JSONObject((HashMap<String, Object>) obj).toString());
|
||||
else
|
||||
user.send(new JSONObject(obj).toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void broadcastMessage(Object obj) {
|
||||
broadcastMessage(obj, null);//Broadcasts the message to ever user
|
||||
}
|
||||
|
||||
|
||||
private boolean hasField(Object obj, String fieldName) {
|
||||
Field[] fields = obj.getClass().getFields();
|
||||
for (Field field : fields) {
|
||||
if (fieldName.equals(field.getName()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Map<String, Object> allFieldsToMap(Object obj) {
|
||||
Map map = new HashMap<String, Object>();
|
||||
try {
|
||||
Field[] fields = obj.getClass().getFields();
|
||||
for (Field field : fields) {
|
||||
map.put(field.getName(), field.get(obj));
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
System.err.println("Illegal Access error:" + e.getStackTrace());
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static void sendFullSettings() {
|
||||
//General settings
|
||||
Map<String, Object> fullSettings = new HashMap<>(allFieldsToMap(SettingsManager.GeneralSettings));
|
||||
fullSettings.put("cameraList", CameraManager.getAllCamerasByName().keySet());
|
||||
try {
|
||||
var currentCamera = CameraManager.getCurrentCamera();
|
||||
fullSettings.putAll(allFieldsToMap(currentCamera.getCurrentPipeline()));
|
||||
fullSettings.put("pipelineList", currentCamera.getPipelines().keySet());
|
||||
fullSettings.put("resolutionList", CameraManager.getResolutionList());
|
||||
fullSettings.put("resolution", currentCamera.getVideoModeIndex());
|
||||
fullSettings.put("FOV", currentCamera.getFOV());
|
||||
fullSettings.put("port", currentCamera.getStreamPort());
|
||||
} catch (CameraException e) {
|
||||
System.err.println("No camera found!");
|
||||
//TODO: add message to ui to inform that there are no cameras
|
||||
}
|
||||
broadcastMessage(fullSettings);
|
||||
}
|
||||
}
|
||||
2
Main/src/main/resources/META-INF/MANIFEST.MF
Normal file
@@ -0,0 +1,2 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: com.chameleonvision.Main
|
||||
|
Before Width: | Height: | Size: 542 KiB After Width: | Height: | Size: 542 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.png><title>Chameleon Vision</title><link href=/css/app.8be123c7.css rel=preload as=style><link href=/js/app.fd9292a1.js rel=preload as=script><link href=/js/chunk-vendors.a3ecb371.js rel=preload as=script><link href=/css/app.8be123c7.css rel=stylesheet></head><body><noscript><strong>We're sorry but Chameleon Vision doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.a3ecb371.js></script><script src=/js/app.fd9292a1.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.png><title>Chameleon Vision</title><link href=/css/app.ae0631ad.css rel=preload as=style><link href=/js/app.a39e3e86.js rel=preload as=script><link href=/js/chunk-vendors.60cc7e7e.js rel=preload as=script><link href=/css/app.ae0631ad.css rel=stylesheet></head><body><noscript><strong>We're sorry but Chameleon Vision doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.60cc7e7e.js></script><script src=/js/app.a39e3e86.js></script></body></html>
|
||||
2
Main/src/main/resources/web/js/app.a39e3e86.js
Normal file
1
Main/src/main/resources/web/js/app.a39e3e86.js.map
Normal file
BIN
Main/target/classes/web/fonts/ionicons.143146fa.woff2
Normal file
BIN
Main/target/classes/web/fonts/ionicons.99ac3308.woff
Normal file
BIN
Main/target/classes/web/fonts/ionicons.d535a25a.ttf
Normal file
870
Main/target/classes/web/img/ionicons.a2c4a261.svg
Normal file
|
After Width: | Height: | Size: 542 KiB |
BIN
Main/target/classes/web/img/logo.e82307fd.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
1
Main/target/classes/web/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.png><title>Chameleon Vision</title><link href=/css/app.ae0631ad.css rel=preload as=style><link href=/js/app.a39e3e86.js rel=preload as=script><link href=/js/chunk-vendors.60cc7e7e.js rel=preload as=script><link href=/css/app.ae0631ad.css rel=stylesheet></head><body><noscript><strong>We're sorry but Chameleon Vision doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.60cc7e7e.js></script><script src=/js/app.a39e3e86.js></script></body></html>
|
||||
11
README.md
@@ -151,4 +151,13 @@ main docs can be found at [google docs](https://docs.google.com/document/d/1qDuw
|
||||
|
||||
* the [robotpy project](https://github.com/robotpy) and mainly the cscore libs
|
||||
|
||||
* basically all of stackoverflow
|
||||
* basically all of stackoverflow
|
||||
|
||||
##License
|
||||
Copyright (C) 2019 Ori Agranat oriagranat9@gmail.com
|
||||
|
||||
|
||||
* This file is part of Chameleon Vision.
|
||||
|
||||
Chameleon Vision can not be copied without the express permission of Ori Agranat
|
||||
Chameleon Vision binaries may be distributed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
from networktables import NetworkTables
|
||||
import tornado.ioloop
|
||||
import logging
|
||||
from app.ChameleonVisionApp import ChameleonApplication
|
||||
from app.classes.SettingsManager import SettingsManager
|
||||
from tornado.options import options
|
||||
import threading
|
||||
import asyncio
|
||||
from app.handlers.CameraHander import CameraHandler
|
||||
|
||||
|
||||
def run_server():
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
tornado.options.parse_command_line()
|
||||
app = ChameleonApplication()
|
||||
print(f"Serving on port {options.port}")
|
||||
app.listen(options.port)
|
||||
tornado.ioloop.IOLoop.current().start()
|
||||
|
||||
|
||||
def run():
|
||||
NetworkTables.startClientTeam(team=settings_manager.general_settings.get("team_number", 1577))
|
||||
port = 5550
|
||||
for cam_name in settings_manager.usb_cameras:
|
||||
CameraHandler(cam_name, port).run()
|
||||
port += 1
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
settings_manager = SettingsManager()
|
||||
run()
|
||||
server_thread = threading.Thread(target=run_server)
|
||||
server_thread.start()
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import os
|
||||
from .handlers.MainHandler import MainHandler
|
||||
from .handlers.SocketHandler import ChameleonWebSocket
|
||||
from tornado.options import define
|
||||
|
||||
define("port", default=8888, help="run on the given port", type=int)
|
||||
|
||||
|
||||
class ChameleonApplication(tornado.web.Application):
|
||||
def __init__(self):
|
||||
handlers = [
|
||||
(r"/", MainHandler),
|
||||
(r"/websocket", ChameleonWebSocket),
|
||||
(r"/(.*)", tornado.web.StaticFileHandler,
|
||||
{"path": r"{0}".format(os.path.join(os.path.dirname(__file__), "site"))}),
|
||||
]
|
||||
|
||||
settings = dict({
|
||||
"template_path": os.path.join(os.path.dirname(__file__), "site"),
|
||||
"static_path": os.path.join(os.path.dirname(__file__), "site"),
|
||||
"debug": True
|
||||
}
|
||||
)
|
||||
|
||||
super(ChameleonApplication, self).__init__(handlers, **settings)
|
||||
@@ -1,10 +0,0 @@
|
||||
class PipelineAlreadyExistsException(Exception):
|
||||
|
||||
def __init__(self, pipe_name):
|
||||
super(f"Pipeline {pipe_name} already exists")
|
||||
|
||||
|
||||
class NoCameraConnectedException(Exception):
|
||||
|
||||
def __init__(self):
|
||||
super("No camera as been detected")
|
||||
@@ -1,263 +0,0 @@
|
||||
import os
|
||||
import json
|
||||
import cv2
|
||||
import cscore
|
||||
import subprocess
|
||||
from cscore._cscore import VideoMode
|
||||
from .Singleton import Singleton
|
||||
from .Exceptions import PipelineAlreadyExistsException, NoCameraConnectedException
|
||||
from ..handlers.IPHandler import ChangeIP
|
||||
|
||||
|
||||
class SettingsManager(metaclass=Singleton):
|
||||
cams = {}
|
||||
usb_cameras = {}
|
||||
usb_cameras_info = {}
|
||||
general_settings = {}
|
||||
cams_port = {}
|
||||
cams_curr_pipeline = {}
|
||||
|
||||
default_pipeline = {
|
||||
"exposure": 50,
|
||||
"brightness": 50,
|
||||
"orientation": "Normal",
|
||||
"hue": [0, 100],
|
||||
"saturation": [0, 100],
|
||||
"value": [0, 100],
|
||||
"erode": False,
|
||||
"dilate": False,
|
||||
"area": [0, 100],
|
||||
"ratio": [0, 20],
|
||||
"extent": [0, 100],
|
||||
"is_binary": 0,
|
||||
"sort_mode": "Largest",
|
||||
"target_group": 'Single',
|
||||
"target_intersection": 'Up',
|
||||
"M": 1,
|
||||
"B": 0
|
||||
}
|
||||
default_general_settings = {
|
||||
"team_number": 1577,
|
||||
"connection_type": "DHCP",
|
||||
"ip": "",
|
||||
"gateway": "",
|
||||
"netmask": "",
|
||||
"hostname": "Chameleon-Vision",
|
||||
"curr_camera": "",
|
||||
"curr_pipeline": ""
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.settings_path = os.path.join(os.getcwd(), "settings")
|
||||
self.cams_path = os.path.join(self.settings_path, "cams")
|
||||
self._init_general_settings()
|
||||
ChangeIP(connection_type=self.general_settings['connection_type'], hostname=self.general_settings['hostname'],
|
||||
ip=self.general_settings['ip'],
|
||||
netmask=self.general_settings['netmask'], gateway=self.general_settings['gateway'])
|
||||
self._init_cameras_info()
|
||||
self._init_usb_cameras()
|
||||
self._init_cameras()
|
||||
self._init_usb_cameras_settings()
|
||||
|
||||
if self.general_settings["curr_camera"] not in self.cams:
|
||||
if len(self.cams) > 0:
|
||||
cam_name = list(self.cams.keys())[0]
|
||||
self.general_settings["curr_camera"] = cam_name
|
||||
self.general_settings["curr_pipeline"] = list(self.cams[cam_name]["pipelines"].keys())[0]
|
||||
else:
|
||||
self.general_settings["curr_camera"] = ""
|
||||
self.general_settings["curr_pipeline"] = ""
|
||||
|
||||
def _init_general_settings(self):
|
||||
try:
|
||||
with open(os.path.join(self.settings_path, 'settings.json')) as setting_file:
|
||||
self.general_settings = json.load(setting_file)
|
||||
except FileNotFoundError:
|
||||
self.general_settings = self.default_general_settings.copy()
|
||||
|
||||
# Initiate our camera's settings
|
||||
def _init_cameras(self):
|
||||
for cam_name in self.usb_cameras_info:
|
||||
if os.path.exists(os.path.join(self.cams_path, cam_name + '.json')):
|
||||
with open(os.path.join(self.cams_path, cam_name + '.json'), 'r') as camera:
|
||||
self.cams[cam_name] = json.load(camera)
|
||||
if len(self.cams[cam_name]["pipelines"]) == 0:
|
||||
self.create_new_pipeline(cam_name=cam_name)
|
||||
else:
|
||||
self.create_new_cam(cam_name)
|
||||
|
||||
# Initiate true usb cameras(filters microphones and double cameras)
|
||||
def _init_cameras_info(self):
|
||||
true_cameras = []
|
||||
usb_devices = cscore.UsbCamera.enumerateUsbCameras()
|
||||
|
||||
for index, device in enumerate(usb_devices):
|
||||
cap = cv2.VideoCapture(device.dev)
|
||||
if cap.isOpened():
|
||||
true_cameras.append(index)
|
||||
cap.release()
|
||||
|
||||
for i in true_cameras:
|
||||
device_name = usb_devices[i].name
|
||||
suffix = 0
|
||||
|
||||
while device_name in self.usb_cameras_info:
|
||||
suffix += 1
|
||||
device_name = f"{device.name}({str(suffix)})"
|
||||
|
||||
self.usb_cameras_info[device_name] = usb_devices[i]
|
||||
|
||||
# Initiate cscore usb devices
|
||||
def _init_usb_cameras(self):
|
||||
for device_name in self.usb_cameras_info:
|
||||
device = self.usb_cameras_info[device_name]
|
||||
|
||||
camera = cscore.UsbCamera(name=device_name, dev=device.dev)
|
||||
|
||||
self.usb_cameras[device_name] = camera
|
||||
|
||||
def _init_usb_cameras_settings(self):
|
||||
for cam_name in self.usb_cameras:
|
||||
self.usb_cameras[cam_name].setPixelFormat(pixelFormat=getattr(VideoMode.PixelFormat, self.cams[cam_name]["video_mode"]["pixel_format"]))
|
||||
self.usb_cameras[cam_name].setFPS(self.cams[cam_name]["video_mode"]["fps"])
|
||||
self.usb_cameras[cam_name].setResolution(width=self.cams[cam_name]["video_mode"]["width"], height=self.cams[cam_name]["video_mode"]["height"])
|
||||
|
||||
# Change usb camera settings
|
||||
def set_camera_settings(self, camera_name, dic):
|
||||
|
||||
if "brightness" in dic:
|
||||
self.usb_cameras[camera_name].setBrightness(dic["brightness"])
|
||||
|
||||
if "exposure" in dic:
|
||||
self.usb_cameras[camera_name].setExposureManual(dic["exposure"])
|
||||
|
||||
if "resolution" in dic:
|
||||
video_mode: VideoMode = self.usb_cameras[camera_name].enumerateVideoModes()[int(dic["resolution"])]
|
||||
self.cams[camera_name]["video_mode"] = {
|
||||
"fps": video_mode.fps,
|
||||
"width": video_mode.width,
|
||||
"height": video_mode.height,
|
||||
"pixel_format": str(video_mode.pixelFormat).split('.')[1]
|
||||
}
|
||||
|
||||
self.usb_cameras[camera_name].setVideoMode(self.usb_cameras[camera_name].enumerateVideoModes()[int(dic["resolution"])])
|
||||
if "FOV" in dic:
|
||||
self.cams[camera_name]["FOV"] = float(dic["FOV"])
|
||||
# Access methods
|
||||
|
||||
def get_curr_pipeline(self):
|
||||
if self.general_settings["curr_pipeline"]:
|
||||
return self.cams[self.general_settings["curr_camera"]]["pipelines"][self.general_settings["curr_pipeline"]]
|
||||
|
||||
raise NoCameraConnectedException()
|
||||
|
||||
def get_resolution_list(self):
|
||||
if self.general_settings["curr_camera"]:
|
||||
str_list = []
|
||||
for val in self.usb_cameras[self.general_settings["curr_camera"]].enumerateVideoModes():
|
||||
str_list.append("{width} X {height} at {fps} fps".format(width=str(val.width),
|
||||
height=str(val.height), fps=str(val.fps)))
|
||||
|
||||
return str_list
|
||||
|
||||
raise NoCameraConnectedException()
|
||||
|
||||
def get_curr_cam(self):
|
||||
if self.general_settings["curr_camera"]:
|
||||
return self.cams[self.general_settings["curr_camera"]]
|
||||
|
||||
raise NoCameraConnectedException()
|
||||
|
||||
def set_curr_camera(self, cam_name):
|
||||
if cam_name in self.cams:
|
||||
self.general_settings["curr_camera"] = cam_name
|
||||
self.general_settings["curr_pipeline"] = list(self.get_curr_cam()["pipelines"].keys())[0]
|
||||
|
||||
def set_curr_pipeline(self, pipe_name):
|
||||
if pipe_name in self.get_curr_cam()["pipelines"]:
|
||||
self.general_settings["curr_pipeline"] = pipe_name
|
||||
|
||||
def change_pipeline_values(self, dic, cam_name=None, pipe_name=None):
|
||||
|
||||
if not cam_name:
|
||||
cam_name = self.general_settings["curr_camera"]
|
||||
|
||||
if not pipe_name:
|
||||
pipe_name = self.general_settings["curr_pipeline"]
|
||||
|
||||
for key in dic:
|
||||
if key in self.default_pipeline:
|
||||
self.cams[cam_name]["pipelines"][pipe_name][key] = dic[key]
|
||||
|
||||
def change_general_settings_values(self, dic):
|
||||
for key in dic['change_general_settings_values']:
|
||||
if key in self.default_general_settings.keys():
|
||||
self.general_settings[key] = dic['change_general_settings_values'][key]
|
||||
self.save_settings()
|
||||
subprocess.call(['reboot'])
|
||||
# after all values has been set change settings
|
||||
|
||||
|
||||
|
||||
# Creators
|
||||
|
||||
def create_new_pipeline(self, pipe_name=None, cam_name=None):
|
||||
|
||||
if not cam_name:
|
||||
cam_name = self.general_settings["curr_camera"]
|
||||
|
||||
if not pipe_name:
|
||||
suffix = 0
|
||||
pipe_name = "pipeline" + str(suffix)
|
||||
|
||||
while pipe_name in self.cams[cam_name]["pipelines"]:
|
||||
suffix += 1
|
||||
pipe_name = "pipeline" + str(suffix)
|
||||
elif self.cams[cam_name]["pipelines"][pipe_name]:
|
||||
raise PipelineAlreadyExistsException(pipe_name)
|
||||
|
||||
self.cams[cam_name]["pipelines"][pipe_name] = self.default_pipeline.copy()
|
||||
|
||||
def create_new_cam(self, cam_name):
|
||||
self.cams[cam_name] = {}
|
||||
self.cams[cam_name]["pipelines"] = {}
|
||||
for i in range(10):
|
||||
self.create_new_pipeline(cam_name=cam_name)
|
||||
|
||||
self.cams[cam_name]["path"] = self.usb_cameras_info[cam_name].otherPaths[0] if len(
|
||||
self.usb_cameras_info[cam_name].otherPaths) == 1 else self.usb_cameras_info[cam_name].otherPaths[1]
|
||||
|
||||
video_mode: VideoMode = self.usb_cameras[cam_name].enumerateVideoModes()[0]
|
||||
self.cams[cam_name]["video_mode"] = {
|
||||
"fps": video_mode.fps,
|
||||
"width": video_mode.width,
|
||||
"height": video_mode.height,
|
||||
"pixel_format": str(video_mode.pixelFormat).split('.')[1],
|
||||
}
|
||||
self.cams[cam_name]['resolution'] = 0
|
||||
self.cams[cam_name]["FOV"] = 60.8
|
||||
|
||||
# Savers
|
||||
|
||||
def save_settings(self):
|
||||
self._save_general_settings()
|
||||
self._save_cameras()
|
||||
|
||||
def _save_cameras(self):
|
||||
|
||||
if not os.path.exists(self.cams_path):
|
||||
os.mkdir(self.cams_path)
|
||||
|
||||
for cam in self.cams:
|
||||
with open(os.path.join(self.cams_path, cam + '.json'), 'w+') as camera:
|
||||
json.dump(self.cams[cam], camera)
|
||||
|
||||
def _save_general_settings(self):
|
||||
if not os.path.exists(self.settings_path):
|
||||
os.mkdir(self.settings_path)
|
||||
|
||||
with open(os.path.join(self.settings_path, 'settings.json'), 'w+') as setting_file:
|
||||
json.dump(self.general_settings, setting_file)
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
class Singleton(type):
|
||||
_instances = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls._instances[cls]
|
||||
@@ -1,252 +0,0 @@
|
||||
import math
|
||||
import cv2
|
||||
import numpy
|
||||
from cscore import CameraServer
|
||||
from app.classes.SettingsManager import SettingsManager
|
||||
from ..handlers.SocketHandler import send_all_async
|
||||
from multiprocessing import Process
|
||||
import threading
|
||||
import zmq
|
||||
import asyncio
|
||||
import time
|
||||
from networktables import NetworkTables
|
||||
import networktables
|
||||
from .VisionHandler import VisionHandler
|
||||
|
||||
|
||||
class CameraHandler:
|
||||
def __init__(self, cam_name, port):
|
||||
#settings vars up for vision loop
|
||||
self.cs = CameraServer.getInstance()
|
||||
self.settings_manager = SettingsManager()
|
||||
self.vision_handler = VisionHandler()
|
||||
self.port = port
|
||||
self.cam_name = cam_name
|
||||
self.image = None
|
||||
self.p_image = None
|
||||
self.table = None
|
||||
self.nt_data = {'valid': False}
|
||||
self.time_stamp = 0
|
||||
|
||||
def run(self):
|
||||
#starting main thread
|
||||
threading.Thread(target=self.thread_proc).start()
|
||||
|
||||
def thread_proc(self):
|
||||
self.settings_manager.cams_curr_pipeline[self.cam_name] = "pipeline0"
|
||||
pipeline = self.settings_manager.cams[self.cam_name]["pipelines"][self.settings_manager.cams_curr_pipeline[self.cam_name]]
|
||||
FOV = self.settings_manager.cams[self.cam_name]["FOV"]
|
||||
|
||||
def change_camera_values(pipeline):
|
||||
self.settings_manager.usb_cameras[self.cam_name].setBrightness(pipeline['brightness'])
|
||||
self.settings_manager.usb_cameras[self.cam_name].setExposureManual(pipeline['exposure'])
|
||||
self.settings_manager.usb_cameras[self.cam_name].setWhiteBalanceAuto()
|
||||
|
||||
def pipeline_listener(table, key, value, is_new):
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
if value in self.settings_manager.cams[self.cam_name]['pipelines'].keys():
|
||||
self.settings_manager.cams_curr_pipeline[self.cam_name] = value
|
||||
change_camera_values(pipeline)
|
||||
if self.cam_name == self.settings_manager.general_settings['curr_camera']:
|
||||
self.settings_manager.general_settings['curr_pipeline'] = value
|
||||
update_settings = self.settings_manager.get_curr_pipeline()
|
||||
update_settings['curr_pipeline'] = self.settings_manager.general_settings["curr_pipeline"]
|
||||
send_all_async(update_settings)
|
||||
else:
|
||||
self.table.putString('Pipeline', self.settings_manager.cams_curr_pipeline[self.cam_name])
|
||||
|
||||
def mode_listener(table, key, value, is_new):
|
||||
if value:
|
||||
change_camera_values({
|
||||
'brightness': 25,
|
||||
'exposure': 15
|
||||
})
|
||||
else:
|
||||
change_camera_values(pipeline)
|
||||
#setting up network table
|
||||
self.table = NetworkTables.getTable("/Chameleon-Vision/" + self.cam_name)
|
||||
#init values for pipeline and driver mode
|
||||
self.table.putString('Pipeline', self.settings_manager.cams_curr_pipeline[self.cam_name])
|
||||
self.table.putBoolean('Driver_Mode', False)
|
||||
self.table.addEntryListenerEx(pipeline_listener, key="Pipeline",
|
||||
flags=networktables.NetworkTablesInstance.NotifyFlags.UPDATE)
|
||||
self.table.addEntryListenerEx(mode_listener, key="Driver_Mode",
|
||||
flags=networktables.NetworkTablesInstance.NotifyFlags.UPDATE)
|
||||
|
||||
# getting video from current camera
|
||||
cv_sink = self.cs.getVideo(camera=self.settings_manager.usb_cameras[self.cam_name])
|
||||
|
||||
width = self.settings_manager.cams[self.cam_name]["video_mode"]["width"]
|
||||
height = self.settings_manager.cams[self.cam_name]["video_mode"]["height"]
|
||||
|
||||
# setting up a video server for camera
|
||||
cv_publish = self.cs.putVideo(name=self.cam_name, width=width, height=height)
|
||||
# saving camera port in cam name dict for usage in client
|
||||
self.settings_manager.cams_port[self.cam_name] = self.cs._sinks['serve_' + self.cam_name].getPort()
|
||||
|
||||
# setting up a zmq connection to the opencv subprocess
|
||||
context = zmq.Context()
|
||||
socket = context.socket(zmq.PAIR)
|
||||
socket.bind('tcp://*:%s' % str(self.port))
|
||||
|
||||
# starting the process with initial values
|
||||
p = Process(target=self.camera_process, args=(self.cam_name, self.port, FOV))
|
||||
p.start()
|
||||
|
||||
change_camera_values(pipeline)
|
||||
|
||||
def _publish_thread():
|
||||
#getting image values and publishing process image and data
|
||||
self.image = numpy.zeros(shape=(width, height, 3), dtype=numpy.uint8)
|
||||
self.p_image = self.image
|
||||
while True:
|
||||
try:
|
||||
self.time_stamp, self.image = cv_sink.grabFrame(self.image)
|
||||
cv_publish.putFrame(self.p_image)
|
||||
self.table.putBoolean('valid', self.nt_data['valid'])
|
||||
# check if point is valid
|
||||
if self.nt_data['valid']:
|
||||
# send the point using network tables
|
||||
self.table.putNumber('pitch', self.nt_data['pitch'])
|
||||
self.table.putNumber('yaw', self.nt_data['yaw'])
|
||||
self.table.putNumber('time_stamp', self.nt_data['time_stamp'])
|
||||
self.table.putNumber('fps', self.nt_data['fps'])
|
||||
# if the selected camera in ui is this cam send the point to the ui
|
||||
except:
|
||||
pass
|
||||
|
||||
def _socket_thread():
|
||||
#publishing to websocket at slower interval
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
while True:
|
||||
time.sleep(0.1)
|
||||
if self.settings_manager.general_settings['curr_camera'] == self.cam_name:
|
||||
try:
|
||||
send_all_async({
|
||||
'raw_point': self.nt_data['raw_point'],
|
||||
'point': {
|
||||
'pitch': self.nt_data['pitch'],
|
||||
'yaw': self.nt_data['yaw'],
|
||||
'fps': self.nt_data['fps']
|
||||
}
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
threading.Thread(target=_publish_thread).start()
|
||||
threading.Thread(target=_socket_thread).start()
|
||||
|
||||
while True:
|
||||
#sending and reciving data from opencv sub process
|
||||
pipeline = self.settings_manager.cams[self.cam_name]["pipelines"][
|
||||
self.settings_manager.cams_curr_pipeline[self.cam_name]]
|
||||
|
||||
socket.send_json(dict(
|
||||
pipeline=pipeline,
|
||||
driver_mode=self.table.getBoolean('Driver_Mode', False)
|
||||
), zmq.SNDMORE)
|
||||
|
||||
socket.send_pyobj((self.time_stamp,self.image))
|
||||
self.p_image = socket.recv_pyobj()
|
||||
self.nt_data = socket.recv_json()
|
||||
|
||||
def camera_process(self, cam_name, port, FOV):
|
||||
from fractions import Fraction
|
||||
#calc fov
|
||||
diagonalView = math.radians(FOV)
|
||||
|
||||
width = self.settings_manager.cams[cam_name]["video_mode"]["width"]
|
||||
height = self.settings_manager.cams[cam_name]["video_mode"]["height"]
|
||||
centerX = (width / 2) - .5
|
||||
centerY = (height / 2) - .5
|
||||
cam_area = width * height
|
||||
|
||||
aspect_fraction = Fraction(width, height)
|
||||
horizontal_ratio = aspect_fraction.numerator
|
||||
vertical_ratio = aspect_fraction.denominator
|
||||
|
||||
horizontalView = math.atan(math.tan(diagonalView / 2) * (horizontal_ratio / diagonalView)) * 2
|
||||
verticalView = math.atan(math.tan(diagonalView / 2) * (vertical_ratio / diagonalView)) * 2
|
||||
|
||||
H_FOCAL_LENGTH = width / (2 * math.tan((horizontalView / 2)))
|
||||
V_FOCAL_LENGTH = height / (2 * math.tan((verticalView / 2)))
|
||||
#setting up zmq socket
|
||||
context = zmq.Context()
|
||||
socket = context.socket(zmq.PAIR)
|
||||
socket.connect('tcp://localhost:%s' % str(port))
|
||||
#setting up filter countours class
|
||||
filter_contours = self.vision_handler.Filter_Contours(center_x=centerX, center_y=centerY)
|
||||
|
||||
x = 1
|
||||
counter = 0
|
||||
start_time = time.time()
|
||||
fps = 0
|
||||
|
||||
while True:
|
||||
obj = socket.recv_json()
|
||||
curr_pipeline = obj['pipeline']
|
||||
driver_mode = obj['driver_mode']
|
||||
time_stamp, image = socket.recv_pyobj()
|
||||
if curr_pipeline['orientation'] == "Inverted":
|
||||
M = cv2.getRotationMatrix2D((width / 2, height / 2), 180, 1)
|
||||
image = cv2.warpAffine(image, M, (width, height))
|
||||
if not driver_mode:
|
||||
hsv_image = self.vision_handler._hsv_threshold(curr_pipeline["hue"],
|
||||
curr_pipeline["saturation"], curr_pipeline["value"],
|
||||
image, curr_pipeline["erode"], curr_pipeline["dilate"])
|
||||
# if table.getBoolean("Driver_Mode", False):
|
||||
contours = self.vision_handler.find_contours(hsv_image)
|
||||
filtered_contours = filter_contours.filter_contours(input_contours=contours, area=curr_pipeline['area'],
|
||||
ratio=curr_pipeline['ratio'],
|
||||
extent=curr_pipeline['extent'],
|
||||
sort_mode=curr_pipeline['sort_mode'], cam_area=cam_area,
|
||||
target_grouping=curr_pipeline['target_group'],
|
||||
target_intersection=
|
||||
curr_pipeline['target_intersection'])
|
||||
|
||||
final_contour = self.vision_handler.output_contour(filtered_contours)
|
||||
|
||||
try:
|
||||
center = final_contour[0]
|
||||
if curr_pipeline["M"] == 1 and curr_pipeline["B"] == 0:
|
||||
center_x = centerX
|
||||
center_y = centerY
|
||||
else:
|
||||
center_x = (center[1] - curr_pipeline['B']) / curr_pipeline["M"]
|
||||
center_y = (center[0] * curr_pipeline["M"]) + curr_pipeline["B"]
|
||||
pitch = self.vision_handler.calculate_pitch(pixel_y=center[1], center_y=center_y, v_focal_length=V_FOCAL_LENGTH)
|
||||
yaw = self.vision_handler.calculate_yaw(pixel_x=center[0], center_x=center_x, h_focal_length=H_FOCAL_LENGTH)
|
||||
valid = True
|
||||
except IndexError:
|
||||
center = None
|
||||
pitch = None
|
||||
yaw = None
|
||||
valid = False
|
||||
|
||||
if curr_pipeline['is_binary']:
|
||||
draw_image = hsv_image
|
||||
else:
|
||||
draw_image = image
|
||||
|
||||
res = self.vision_handler.draw_image(input_image=draw_image, contour=final_contour)
|
||||
else:
|
||||
res = image
|
||||
center = None
|
||||
pitch = None
|
||||
yaw = None
|
||||
valid = False
|
||||
|
||||
socket.send_pyobj(res)
|
||||
socket.send_json(dict(
|
||||
pitch=pitch,
|
||||
yaw=yaw,
|
||||
valid=valid,
|
||||
raw_point=center,
|
||||
fps=fps,
|
||||
time_stamp=time_stamp
|
||||
))
|
||||
counter += 1
|
||||
if (time.time() - start_time) > x:
|
||||
fps = (counter / (time.time() - start_time))
|
||||
counter = 0
|
||||
start_time = time.time()
|
||||
@@ -1,49 +0,0 @@
|
||||
import subprocess
|
||||
import netifaces
|
||||
|
||||
|
||||
class ChangeIP:
|
||||
def __init__(self, connection_type, ip, netmask, gateway, hostname):
|
||||
|
||||
adapter = self.find_adapter()
|
||||
if adapter is not None:
|
||||
self.shutdown_adapter(adapter)
|
||||
|
||||
if connection_type == "DHCP":
|
||||
self.change_to_dhcp(adapter=adapter)
|
||||
|
||||
elif connection_type == "Static":
|
||||
self.change_to_static(adapter=adapter, ip=ip, netmask=netmask, gateway=gateway)
|
||||
|
||||
self.start_adapter(adapter)
|
||||
else:
|
||||
print("not connected to robot radio cannot set ip")
|
||||
self.change_hostname(hostname=hostname)
|
||||
|
||||
@staticmethod
|
||||
def change_to_dhcp(adapter):
|
||||
subprocess.call(['dhclient',"-r", adapter])
|
||||
|
||||
@staticmethod
|
||||
def change_to_static(adapter, ip, netmask, gateway):
|
||||
subprocess.call(['ifconfig', adapter, ip, 'netmask', netmask])
|
||||
subprocess.call(['route', 'add', 'default', 'gw', gateway, adapter])
|
||||
|
||||
@staticmethod
|
||||
def shutdown_adapter(adapter):
|
||||
subprocess.call(['ifconfig', adapter, 'down'])
|
||||
@staticmethod
|
||||
def start_adapter(adapter):
|
||||
subprocess.call(['ifconfig', adapter, 'up'])
|
||||
|
||||
@staticmethod
|
||||
def find_adapter():
|
||||
for i_name in netifaces.interfaces():
|
||||
interface = netifaces.ifaddresses(i_name)[netifaces.AF_INET][0]
|
||||
address = interface['addr'].split('.')[0]
|
||||
if address == "10":
|
||||
return str(i_name)
|
||||
|
||||
@staticmethod
|
||||
def change_hostname(hostname):
|
||||
subprocess.call(['hostnamectl', 'set-hostname', hostname])
|
||||
@@ -1,6 +0,0 @@
|
||||
import tornado.web
|
||||
|
||||
|
||||
class MainHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
self.render("index.html")
|
||||
@@ -1,123 +0,0 @@
|
||||
import tornado.websocket
|
||||
import json
|
||||
from ..classes.Exceptions import NoCameraConnectedException
|
||||
from ..classes.SettingsManager import SettingsManager
|
||||
|
||||
|
||||
web_socket_clients = set()
|
||||
|
||||
|
||||
def send_all_async(message):
|
||||
for ws in web_socket_clients:
|
||||
try:
|
||||
ws.write_message(json.dumps(message))
|
||||
except AssertionError as a:
|
||||
pass
|
||||
|
||||
|
||||
class ChameleonWebSocket(tornado.websocket.WebSocketHandler):
|
||||
|
||||
actions = {}
|
||||
|
||||
set_this_camera_settings = ["exposure", "brightness"]
|
||||
|
||||
def __init__(self, application, request, **kwargs):
|
||||
super().__init__(application, request, **kwargs)
|
||||
self.settings_manager = SettingsManager()
|
||||
self.init_actions()
|
||||
|
||||
def init_actions(self):
|
||||
self.actions["change_pipeline_values"] = self.change_pipeline_values
|
||||
self.actions["change_general_settings_values"] = self.settings_manager.change_general_settings_values
|
||||
self.actions["curr_camera"] = self.change_curr_camera
|
||||
self.actions["curr_pipeline"] = self.change_curr_pipeline
|
||||
self.actions['resolution'] = self.set_resolution
|
||||
self.actions['FOV'] = self.set_fov
|
||||
|
||||
def open(self):
|
||||
self.send_full_settings()
|
||||
if self not in web_socket_clients:
|
||||
web_socket_clients.add(self)
|
||||
|
||||
print("WebSocket opened")
|
||||
|
||||
def on_message(self, message):
|
||||
try:
|
||||
message_dic = json.loads(message)
|
||||
|
||||
for key in message_dic:
|
||||
self.actions.get(key, self.actions["change_pipeline_values"])(message_dic)
|
||||
print(message)
|
||||
except Exception as e:
|
||||
print("crash " + e)
|
||||
|
||||
def on_close(self):
|
||||
self.settings_manager.save_settings()
|
||||
if self in web_socket_clients:
|
||||
web_socket_clients.remove(self)
|
||||
print("WebSocket closed")
|
||||
|
||||
def check_origin(self, origin):
|
||||
return True
|
||||
|
||||
def set_resolution(self, message):
|
||||
self.settings_manager.get_curr_cam()['resolution'] = message['resolution']
|
||||
SettingsManager().set_camera_settings(camera_name=SettingsManager().general_settings['curr_camera'],
|
||||
dic=message)
|
||||
self.settings_manager.save_settings()
|
||||
|
||||
def set_fov(self, message):
|
||||
self.settings_manager.get_curr_cam()['FOV'] = message['FOV']
|
||||
self.settings_manager.save_settings()
|
||||
|
||||
def send_curr_pipeline(self):
|
||||
try:
|
||||
self.write_message(self.settings_manager.get_curr_pipeline())
|
||||
except NoCameraConnectedException:
|
||||
# TODO: return something if no camera connected
|
||||
self.write_message("No camera connected")
|
||||
|
||||
def send_curr_cam(self):
|
||||
try:
|
||||
self.write_message(self.settings_manager.get_curr_cam())
|
||||
except NoCameraConnectedException:
|
||||
# TODO: return something if no camera connected
|
||||
self.write_message("No camera connected")
|
||||
|
||||
def send_curr_port(self):
|
||||
self.write_message({
|
||||
'port': self.settings_manager.cams_port[self.settings_manager.general_settings["curr_camera"]]
|
||||
})
|
||||
|
||||
def send_full_settings(self):
|
||||
full_settings = self.settings_manager.general_settings.copy()
|
||||
full_settings["cameraList"] = list(self.settings_manager.cams.copy().keys())
|
||||
try:
|
||||
full_settings.update(self.settings_manager.get_curr_pipeline())
|
||||
full_settings["pipelineList"] = list(self.settings_manager.cams[self.settings_manager.general_settings["curr_camera"]]["pipelines"].keys())
|
||||
full_settings["resolutionList"] = self.settings_manager.get_resolution_list()
|
||||
full_settings['resolution'] = self.settings_manager.get_curr_cam()['resolution']
|
||||
full_settings['FOV'] = self.settings_manager.get_curr_cam()['FOV']
|
||||
full_settings['port'] = self.settings_manager.cams_port[self.settings_manager.general_settings["curr_camera"]]
|
||||
except NoCameraConnectedException:
|
||||
# TODO: return something if no camera connected
|
||||
full_settings["data"] = None
|
||||
|
||||
self.write_message(full_settings)
|
||||
|
||||
def change_curr_camera(self, dic):
|
||||
self.settings_manager.set_curr_camera(cam_name=dic["curr_camera"])
|
||||
self.send_curr_port()
|
||||
self.send_curr_cam()
|
||||
|
||||
def change_curr_pipeline(self, dic):
|
||||
self.settings_manager.set_curr_pipeline(pipe_name=dic["curr_pipeline"])
|
||||
self.settings_manager.cams_curr_pipeline[self.settings_manager.general_settings['curr_camera']] = dic["curr_pipeline"]
|
||||
self.send_curr_pipeline()
|
||||
|
||||
def change_pipeline_values(self, dic):
|
||||
self.settings_manager.change_pipeline_values(dic)
|
||||
for key in self.set_this_camera_settings:
|
||||
if key in dic:
|
||||
self.settings_manager.set_camera_settings(self.settings_manager.general_settings["curr_camera"],
|
||||
dic)
|
||||
@@ -1,221 +0,0 @@
|
||||
import cv2
|
||||
import numpy
|
||||
import math
|
||||
from enum import Enum, unique
|
||||
|
||||
|
||||
class VisionHandler():
|
||||
def __init__(self):
|
||||
self.kernel = numpy.ones((5, 5), numpy.uint8)
|
||||
|
||||
def _hsv_threshold(self, hue: list, saturation: list, value: list, img: numpy.ndarray, is_erode: bool,
|
||||
is_dilate: bool):
|
||||
|
||||
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
|
||||
thresh = cv2.inRange(hsv, (hue[0], saturation[0], value[0]), (hue[1], saturation[1], value[1]))
|
||||
erode_img = cv2.erode(thresh, kernel=self.kernel, iterations=is_erode)
|
||||
dilate_img = cv2.dilate(erode_img, kernel=self.kernel, iterations=is_dilate)
|
||||
return dilate_img
|
||||
|
||||
def find_contours(self, binary_img: numpy.ndarray):
|
||||
|
||||
_,contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_TC89_L1)
|
||||
return contours
|
||||
|
||||
class Filter_Contours:
|
||||
def __init__(self,center_x, center_y):
|
||||
self.sort_mode = self.SortMode(center_x=center_x, center_y=center_y)
|
||||
self.center_y = center_y
|
||||
self.center_x = center_x
|
||||
|
||||
class SortMode:
|
||||
def __init__(self, center_x, center_y):
|
||||
self.center_x = center_x
|
||||
self.center_y = center_y
|
||||
|
||||
@classmethod
|
||||
def moment_x(cls,contour):
|
||||
M = cv2.moments(contour)
|
||||
try:
|
||||
x = float(M['m10'] / M['m00'])
|
||||
except ZeroDivisionError:
|
||||
x = 0
|
||||
return x
|
||||
|
||||
@classmethod
|
||||
def moment_y(cls, contour):
|
||||
M = cv2.moments(contour)
|
||||
try:
|
||||
y = float(M['m01'] / M['m00'])
|
||||
except ZeroDivisionError:
|
||||
y = 0
|
||||
return y
|
||||
|
||||
@classmethod
|
||||
def calc_distance(cls,contour, center_x, center_y):
|
||||
M = cv2.moments(contour)
|
||||
try:
|
||||
x = int(M['m10'] / M['m00'])
|
||||
except ZeroDivisionError:
|
||||
x = 0
|
||||
try:
|
||||
y = int(M['m01'] / M['m00'])
|
||||
except ZeroDivisionError:
|
||||
y = 0
|
||||
# this function was suggested by my girlfriend maya jugend that i really love
|
||||
return math.sqrt((center_x-x)**2 + (center_y-y)**2)
|
||||
|
||||
def Largest(self, input_contours):
|
||||
return sorted(input_contours, key=lambda x: cv2.contourArea(x), reverse=True)
|
||||
|
||||
def Smallest(self, input_contours):
|
||||
return sorted(input_contours, key=lambda x: cv2.contourArea(x))
|
||||
|
||||
def Highest(self, input_contours):
|
||||
return sorted(input_contours, key=lambda x: self.moment_y(x))
|
||||
|
||||
def Lowest(self, input_contours):
|
||||
return sorted(input_contours, key=lambda x: self.moment_y(x),reverse=True)
|
||||
|
||||
def Rightmost(self, input_contours):
|
||||
return sorted(input_contours, key=lambda x: self.moment_x(x), reverse=True)
|
||||
|
||||
def Leftmost(self, input_contours):
|
||||
return sorted(input_contours, key=lambda x: self.moment_x(x))
|
||||
|
||||
def Closest(self, input_contours):
|
||||
return sorted(input_contours, key=lambda x: self.calc_distance(x, center_x=self.center_x,
|
||||
center_y=self.center_y), reverse=True)
|
||||
|
||||
def filter_contours(self, input_contours, cam_area, area, ratio, extent, sort_mode, target_grouping,
|
||||
target_intersection):
|
||||
class TargetGroup(Enum):
|
||||
Single = 1
|
||||
Dual = 2
|
||||
Triple = 3
|
||||
Quadruple = 4
|
||||
Quintuple = 6
|
||||
|
||||
def group_target(i_contours, target_group, intersection_point):
|
||||
|
||||
def is_intersecting(contour_a, contour_b, intersection_direction):
|
||||
|
||||
[vx_a, vy_a, x0_a, y0_a] = cv2.fitLine(contour_a, cv2.DIST_L2, 0, 0.01, 0.01)
|
||||
[vx_b, vy_b, x0_b, y0_b] = cv2.fitLine(contour_b, cv2.DIST_L2, 0, 0.01, 0.01)
|
||||
# getting line data of both contours
|
||||
m_a = vy_a / vx_a
|
||||
m_b = vy_b / vx_b
|
||||
# calculating slope of both lines
|
||||
try:
|
||||
intersection_x = ((m_a * x0_a) - y0_a - (m_b * x0_b) + y0_b) / (m_a - m_b)
|
||||
except ZeroDivisionError:
|
||||
if intersection_direction == 'Parallel':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
intersection_y = (m_a * (intersection_x - x0_a)) + y0_a
|
||||
# finding intersection point
|
||||
if intersection_direction == 'Up':
|
||||
if intersection_y < self.center_y:
|
||||
return True
|
||||
elif intersection_direction == 'Down':
|
||||
if intersection_y > self.center_y:
|
||||
return True
|
||||
elif intersection_direction == 'Left':
|
||||
if intersection_x < self.center_x:
|
||||
return True
|
||||
elif intersection_direction == 'Right':
|
||||
if intersection_x > self.center_x:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
if target_group != TargetGroup.Single:
|
||||
f_contour_list = []
|
||||
for index, g_contour in enumerate(i_contours):
|
||||
final_contour = g_contour
|
||||
for c in range(target_group.value - 1):
|
||||
try:
|
||||
first_contour = i_contours[index + c]
|
||||
second_contour = i_contours[index + c + 1]
|
||||
except IndexError:
|
||||
final_contour = []
|
||||
break
|
||||
if is_intersecting(first_contour, second_contour, intersection_point):
|
||||
final_contour = numpy.concatenate((final_contour, second_contour))
|
||||
|
||||
else:
|
||||
final_contour = []
|
||||
break
|
||||
if final_contour != []:
|
||||
f_contour_list.append(final_contour)
|
||||
|
||||
return f_contour_list
|
||||
else:
|
||||
return i_contours
|
||||
|
||||
'''start of the first filtration of contours'''
|
||||
filtered_contours = []
|
||||
for contour in input_contours:
|
||||
try:
|
||||
contour_area = cv2.contourArea(contour)
|
||||
target_area = float(contour_area / cam_area)*100
|
||||
|
||||
if target_area >= area[1] or target_area <= area[0]:
|
||||
continue
|
||||
|
||||
rect = cv2.minAreaRect(contour)
|
||||
bounding_rect_area = rect[1][0] * rect[1][1]
|
||||
try:
|
||||
target_fullness = float(contour_area / bounding_rect_area)*100
|
||||
except ZeroDivisionError:
|
||||
target_fullness = 0
|
||||
|
||||
if target_fullness <= extent[0] or target_fullness >= extent[1]:
|
||||
continue
|
||||
try:
|
||||
aspect_ratio = float(rect[1][0]/rect[1][1])
|
||||
except ZeroDivisionError:
|
||||
aspect_ratio = 0
|
||||
if aspect_ratio <= ratio[0] or aspect_ratio >= ratio[1]:
|
||||
continue
|
||||
|
||||
filtered_contours.append(contour)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
continue
|
||||
#checking for contour grouping before sorting
|
||||
grouped_contours = group_target(filtered_contours, TargetGroup[target_grouping], target_intersection)
|
||||
try:
|
||||
sorted_contours = getattr(self.sort_mode, sort_mode)(grouped_contours)
|
||||
except TypeError:
|
||||
sorted_contours = []
|
||||
return sorted_contours
|
||||
|
||||
def output_contour(self, sorted_contours):
|
||||
if len(sorted_contours) > 0:
|
||||
selected_contour = sorted_contours[0]
|
||||
rect = cv2.minAreaRect(selected_contour)
|
||||
else:
|
||||
return []
|
||||
return rect
|
||||
|
||||
def draw_image(self, input_image, contour):
|
||||
if len(input_image.shape)<3:
|
||||
input_image = cv2.cvtColor(input_image, cv2.COLOR_GRAY2RGB)
|
||||
if contour != []:
|
||||
box = cv2.boxPoints(contour)
|
||||
box = numpy.int0(box)
|
||||
cv2.drawContours(input_image, [box], 0, (0, 0, 255), 3)
|
||||
|
||||
# center_point = (int(rectangle[0][0]), int(rectangle[0][1]))
|
||||
# cv2.circle(input_image, center_point, 0, (0, 255, 0), thickness=3, lineType=8, shift=0)
|
||||
return input_image
|
||||
|
||||
def calculate_pitch(self, pixel_y, center_y, v_focal_length):
|
||||
pitch = math.degrees(math.atan((pixel_y - center_y) / v_focal_length))
|
||||
pitch *= -1
|
||||
return pitch
|
||||
|
||||
def calculate_yaw(self, pixel_x, center_x, h_focal_length):
|
||||
yaw = math.degrees(math.atan((pixel_x - center_x) / h_focal_length))
|
||||
return yaw
|
||||
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,4 +0,0 @@
|
||||
tornado
|
||||
pyzmq
|
||||
robotpy-cscore
|
||||
netifaces
|
||||
@@ -67,6 +67,20 @@
|
||||
this.$refs.menu.updateOpened();
|
||||
this.$refs.menu.updateActiveName();
|
||||
})
|
||||
},
|
||||
isEquale(message,prop){
|
||||
if(typeof (this.$store.state[prop]) == "object"){
|
||||
for(var i = this.$store.state[prop].length; i--;) {
|
||||
if(this.$store.state[prop][i] !== message[prop][i]){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else{
|
||||
if(this.$store.state[prop] != message[prop]){
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -86,9 +100,11 @@
|
||||
let message = JSON.parse(data.data);
|
||||
for (var prop in message){
|
||||
if(message.hasOwnProperty(prop)){
|
||||
this.$store.state[prop] = message[prop];
|
||||
this.$store.state[prop] = message[prop];
|
||||
// console.log(message);
|
||||
|
||||
}
|
||||
console.log(data.data);
|
||||
|
||||
}
|
||||
}
|
||||
catch{
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<chselect title="camera" :list="cameraList" Xkey="curr_camera"></chselect>
|
||||
</Col>
|
||||
<Col span="12">
|
||||
<chselect title="pipline" :list="pipelineList" Xkey="curr_pipeline"></chselect>
|
||||
<chselect title="pipeline" :list="pipelineList" Xkey="curr_pipeline"></chselect>
|
||||
</Col>
|
||||
</Row>
|
||||
</Header>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<chrange class="spacing" title="Extent" Xkey="extent"></chrange>
|
||||
<chselect class="spacing" title="Target Group" Xkey="target_group"
|
||||
:list="['Single','Dual','Triple','Quadruple','Quintuple']"></chselect>
|
||||
<chselect class="spacing" title="Target Intersaction" Xkey="target_intersection"
|
||||
<chselect class="spacing" title="Target Intersection" Xkey="target_intersection"
|
||||
:list="['Up','Down','Left','Right','Parallel']" :isDisabled="isSingle"></chselect>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -44,7 +44,7 @@ import chrange from './ch-range.vue'
|
||||
let m = (this.pointB[1] - this.pointA[1]) / (this.pointB[0] - this.pointA[0]);
|
||||
let b = this.pointA[1] - (m * this.pointA[0]);
|
||||
if(isNaN(m) === false && isNaN(b) === false){
|
||||
this.sendSlope(m,b);
|
||||
this.sendSlope(m,b,true);
|
||||
} else{
|
||||
this.$Message.error("Point A and B are to close apart");
|
||||
}
|
||||
@@ -53,13 +53,14 @@ import chrange from './ch-range.vue'
|
||||
}
|
||||
},
|
||||
clearPoints:function(){
|
||||
this.sendSlope(1,0);
|
||||
this.sendSlope(1,0,false);
|
||||
this.pointA = undefined;
|
||||
this.pointB = undefined;
|
||||
},
|
||||
sendSlope(m,b){
|
||||
sendSlope(m,b,valid){
|
||||
this.$socket.sendObj({'M':m});
|
||||
this.$socket.sendObj({'B':b});
|
||||
this.$socket.sendObj({'is_calibrated':valid});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
10
install.sh
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
apt-get update
|
||||
apt-get dist-upgrade
|
||||
apt-get upgrade
|
||||
apt-get install python3-pip python3-dev cmake zip unzip build-essential git libnss-mdns --fix-missing
|
||||
apt-get install python3-numpy
|
||||
apt-get install python3-opencv
|
||||
pip3 install robotpy-cscore
|
||||
pip3 install pyzmq
|
||||
pip3 install tornado
|
||||
96
package-lock.json
generated
@@ -1,96 +0,0 @@
|
||||
{
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"async-validator": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-1.10.1.tgz",
|
||||
"integrity": "sha512-VLiLKZuJc8VIeAMC3YobVsZov8XPNhbwyIkKjhPW5cFnhZXH+HHJpkE270YMD/6zJIOJXUN/Cq0t3fR7XPwaDQ==",
|
||||
"requires": {
|
||||
"babel-runtime": "6.x"
|
||||
}
|
||||
},
|
||||
"babel-runtime": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
||||
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
|
||||
"requires": {
|
||||
"core-js": "^2.4.0",
|
||||
"regenerator-runtime": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"batch-processor": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz",
|
||||
"integrity": "sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg="
|
||||
},
|
||||
"core-js": {
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
|
||||
"integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A=="
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
|
||||
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
|
||||
},
|
||||
"element-resize-detector": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.2.0.tgz",
|
||||
"integrity": "sha512-UmhNB8sIJVZeg56gEjgmMd6p37sCg8j8trVW0LZM7Wzv+kxQ5CnRHcgRKBTB/kFUSn3e7UP59kl2V2U8Du1hmg==",
|
||||
"requires": {
|
||||
"batch-processor": "1.0.0"
|
||||
}
|
||||
},
|
||||
"iview": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/iview/-/iview-3.3.0.tgz",
|
||||
"integrity": "sha512-PyqhfxEO9/4rcDNZ1FhMMGjcKh/y/F1p/00/UXrNxXBnih4rIb8GxgxYk0Y14bzuCL/AuMAisOBbQm51o5iOlQ==",
|
||||
"requires": {
|
||||
"async-validator": "^1.10.0",
|
||||
"deepmerge": "^2.2.1",
|
||||
"element-resize-detector": "^1.2.0",
|
||||
"js-calendar": "^1.2.3",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"popper.js": "^1.14.6",
|
||||
"tinycolor2": "^1.4.1",
|
||||
"v-click-outside-x": "^3.5.6"
|
||||
}
|
||||
},
|
||||
"js-calendar": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/js-calendar/-/js-calendar-1.2.3.tgz",
|
||||
"integrity": "sha512-dAA1/Zbp4+c5E+ARCVTIuKepXsNLzSYfzvOimiYD4S5eeP9QuplSHLcdhfqFSwyM1o1u6ku6RRRCyaZ0YAjiBw=="
|
||||
},
|
||||
"lodash.throttle": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
|
||||
},
|
||||
"popper.js": {
|
||||
"version": "1.14.7",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.7.tgz",
|
||||
"integrity": "sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ=="
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
|
||||
},
|
||||
"tinycolor2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz",
|
||||
"integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g="
|
||||
},
|
||||
"undefined": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/undefined/-/undefined-0.1.0.tgz",
|
||||
"integrity": "sha1-m3BqSzKtMMIMpP5l3cu72sMr3tA="
|
||||
},
|
||||
"v-click-outside-x": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/v-click-outside-x/-/v-click-outside-x-3.7.1.tgz",
|
||||
"integrity": "sha512-WmUgmcIXr9clVpm1AYS/FgHtcDicfnfoxgQCNg4O6vfk9GVnxA0vSqO321ogUo0b7czYTidj7fQENvWFMWOkUg=="
|
||||
}
|
||||
}
|
||||
}
|
||||