Merge branch 'class-abstraction' into dev

This commit is contained in:
Banks Troutman
2019-11-27 17:40:56 -05:00
83 changed files with 3387 additions and 1720 deletions

5
.gitignore vendored
View File

@@ -111,9 +111,8 @@ Main/target
New client/chameleon-client/node_modules/
Main/dependency-reduced-pom.xml
Main/src/main/java/META-INF
Main/.settings/org.eclipse.jdt.core.prefs
Main/.classpath
Main/.project
*.prefs

437
LICENSE Normal file
View File

@@ -0,0 +1,437 @@
Attribution-NonCommercial-ShareAlike 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-NonCommercial-ShareAlike 4.0 International Public License
("Public License"). To the extent this Public License may be
interpreted as a contract, You are granted the Licensed Rights in
consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the
Licensor receives from making the Licensed Material available under
these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution, NonCommercial, and ShareAlike.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
k. NonCommercial means not primarily intended for or directed towards
commercial advantage or monetary compensation. For purposes of
this Public License, the exchange of the Licensed Material for
other material subject to Copyright and Similar Rights by digital
file-sharing or similar means is NonCommercial provided there is
no payment of monetary compensation in connection with the
exchange.
l. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
m. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part, for NonCommercial purposes only; and
b. produce, reproduce, and Share Adapted Material for
NonCommercial purposes only.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties, including when
the Licensed Material is used other than for NonCommercial
purposes.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
b. ShareAlike.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-NC-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database for NonCommercial purposes
only;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.

5
Main/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
bin/*
.settings/*
.project
.classpath
*.prefs

View File

@@ -1,11 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" 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" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/classes" />
<excludeFolder url="file://$MODULE_DIR$/settings" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
@@ -39,27 +42,34 @@
<orderEntry type="library" name="Maven: org.msgpack:msgpack-core:0.8.18" level="project" />
<orderEntry type="library" name="Maven: org.msgpack:jackson-dataformat-msgpack:0.8.18" level="project" />
<orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.9" 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" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-java:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxaarch64bionic:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxraspbian:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxx86-64:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:osxx86-64:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:windowsx86-64:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cameraserver:cameraserver-java:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-java:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:osxx86-64:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxraspbian:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxx86-64:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:windowsx86-64:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.wpiutil:wpiutil-java:2019.4.1-213-g56d782b" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-java:3.4.7-1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:linuxaarch64bionic:3.4.7-1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:linuxraspbian:3.4.7-1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:linuxx86-64:3.4.7-1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:osxx86-64:3.4.7-1" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:windowsx86-64:3.4.7-1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.10.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.10.1" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.10.1" level="project" />
<orderEntry type="library" name="Maven: org.junit.jupiter:junit-jupiter-engine:5.5.2" level="project" />
<orderEntry type="library" name="Maven: org.apiguardian:apiguardian-api:1.1.0" level="project" />
<orderEntry type="library" name="Maven: org.junit.platform:junit-platform-engine:1.5.2" level="project" />
<orderEntry type="library" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
<orderEntry type="library" name="Maven: org.junit.platform:junit-platform-commons:1.5.2" level="project" />
<orderEntry type="library" name="Maven: org.junit.jupiter:junit-jupiter-api:5.5.2" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-java:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxaarch64bionic:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxraspbian:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:linuxx86-64:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:osxx86-64:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cscore:cscore-jni:windowsx86-64:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.cameraserver:cameraserver-java:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-java:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:osxx86-64:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxraspbian:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxx86-64:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:linuxaarch64bionic:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.ntcore:ntcore-jni:windowsx86-64:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.wpiutil:wpiutil-java:2020.1.1-beta-2-98-gb058dcf" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-java:3.4.7-2" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:linuxaarch64bionic:3.4.7-2" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:linuxraspbian:3.4.7-2" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:linuxx86-64:3.4.7-2" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:osxx86-64:3.4.7-2" level="project" />
<orderEntry type="library" name="Maven: edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:windowsx86-64:3.4.7-2" level="project" />
</component>
</module>

View File

@@ -85,12 +85,6 @@
<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>
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack-core</artifactId>
@@ -106,10 +100,29 @@
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0.pr1</version>
<version>2.10.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
</dependency>
<!-- supported platforms for wpilib JNI classifiers
@@ -123,37 +136,37 @@
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-java</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
</dependency>
<!--frc cscore interface libs-->
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-jni</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>linuxaarch64bionic</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-jni</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>linuxraspbian</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-jni</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>linuxx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-jni</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>osxx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.cscore</groupId>
<artifactId>cscore-jni</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>windowsx86-64</classifier>
</dependency>
@@ -161,39 +174,45 @@
<dependency>
<groupId>edu.wpi.first.cameraserver</groupId>
<artifactId>cameraserver-java</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
</dependency>
<!--frc network table java libs-->
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-java</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
</dependency>
<!--frc network tables interface libs-->
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-jni</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>osxx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-jni</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>linuxraspbian</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-jni</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>linuxx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-jni</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>linuxaarch64bionic</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.ntcore</groupId>
<artifactId>ntcore-jni</artifactId>
<version>2020.1.1-beta-2-98-gb058dcf</version>
<classifier>windowsx86-64</classifier>
</dependency>
@@ -201,43 +220,43 @@
<dependency>
<groupId>edu.wpi.first.wpiutil</groupId>
<artifactId>wpiutil-java</artifactId>
<version>2019.4.1-213-g56d782b</version>
<version>2020.1.1-beta-2-98-gb058dcf</version>
</dependency>
<!-- WPI OpenCV for all supported platforms -->
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2020.opencv</groupId>
<artifactId>opencv-java</artifactId>
<version>3.4.7-1</version>
<version>3.4.7-2</version>
</dependency>
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2020.opencv</groupId>
<artifactId>opencv-jni</artifactId>
<version>3.4.7-1</version>
<version>3.4.7-2</version>
<classifier>linuxaarch64bionic</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2020.opencv</groupId>
<artifactId>opencv-jni</artifactId>
<version>3.4.7-1</version>
<version>3.4.7-2</version>
<classifier>linuxraspbian</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2020.opencv</groupId>
<artifactId>opencv-jni</artifactId>
<version>3.4.7-1</version>
<version>3.4.7-2</version>
<classifier>linuxx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2020.opencv</groupId>
<artifactId>opencv-jni</artifactId>
<version>3.4.7-1</version>
<version>3.4.7-2</version>
<classifier>osxx86-64</classifier>
</dependency>
<dependency>
<groupId>edu.wpi.first.thirdparty.frc2020.opencv</groupId>
<artifactId>opencv-jni</artifactId>
<version>3.4.7-1</version>
<version>3.4.7-2</version>
<classifier>windowsx86-64</classifier>
</dependency>
</dependencies>

View File

@@ -0,0 +1,19 @@
package com.chameleonvision;
public class Debug {
private Debug() {}
private static boolean isTestMode() {
return Main.testMode;
}
public static void printInfo(String infoMessage) {
if (isTestMode()) {
System.out.println(infoMessage);
}
}
public static void printInfo(String smallInfo, String largeInfo) {
System.out.println(isTestMode() ? String.format("%s - %s" , smallInfo, largeInfo) : smallInfo);
}
}

View File

@@ -1,10 +1,10 @@
package com.chameleonvision;
import com.chameleonvision.config.ConfigManager;
import com.chameleonvision.network.NetworkManager;
import com.chameleonvision.settings.Platform;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.util.Platform;
import com.chameleonvision.util.Utilities;
import com.chameleonvision.vision.camera.CameraManager;
import com.chameleonvision.vision.VisionManager;
import com.chameleonvision.web.Server;
import edu.wpi.cscore.CameraServerCvJNI;
import edu.wpi.cscore.CameraServerJNI;
@@ -14,13 +14,15 @@ import edu.wpi.first.networktables.NetworkTableInstance;
import java.io.IOException;
import java.util.function.Consumer;
import static com.chameleonvision.util.Platform.CurrentPlatform;
public class Main {
private static final String PORT_KEY = "--port"; // expects integer
private static final String NT_SERVERMODE_KEY = "--nt-servermode"; // no args for this setting
private static final String NT_CLIENTMODESERVER_KEY = "--nt-client-server"; // expects String representing an IP address (hostnames will be rejected!)
private static final String NETWORK_MANAGE_KEY = "--unmanage-network"; // no args for this setting
private static final String IGNORE_ROOT = "--ignore-root"; // no args for this setting
private static final String IGNORE_ROOT_KEY = "--ignore-root"; // no args for this setting
private static final String TEST_MODE_KEY = "--cv-development";
private static final int DEFAULT_PORT = 5800;
@@ -28,6 +30,7 @@ public class Main {
private static boolean manageNetwork = true;
private static boolean ignoreRoot = false;
private static String ntClientModeServer = null;
public static boolean testMode = false;
private static class NTLogger implements Consumer<LogMessage> {
@@ -42,8 +45,6 @@ public class Main {
}
}
private static final Platform CurrentPlatform = Platform.getCurrentPlatform();
private static void handleArgs(String[] args) {
for (int i = 0; i < args.length; i++) {
var key = args[i].toLowerCase();
@@ -51,7 +52,6 @@ public class Main {
// this switch handles arguments with a value. Add any settings with a value here.
switch (key) {
case PORT_KEY:
case NT_CLIENTMODESERVER_KEY:
var potentialValue = args[i + 1];
// ensures this "value" isnt null, blank, nor another argument
@@ -62,21 +62,14 @@ public class Main {
break;
case NT_SERVERMODE_KEY:
case NETWORK_MANAGE_KEY:
case IGNORE_ROOT:
case IGNORE_ROOT_KEY:
case TEST_MODE_KEY:
// nothing
break;
}
// this switch actually handles the arguments.
switch (key) {
case PORT_KEY:
System.out.println("INFO - The \"--port\" argument is currently disabled.");
// try {
// if (value == null) throw new Exception("Bad or No argument value");
// webserverPort = Integer.parseInt(value);
// } catch (Exception ex) {
// System.err.printf("Argument for port was invalid, starting server at port %d\n", DEFAULT_PORT);
// }
break;
case NT_SERVERMODE_KEY:
ntServerMode = true;
break;
@@ -84,12 +77,12 @@ public class Main {
if (value != null) {
if (value.equals("localhost")) {
ntClientModeServer = "127.0.0.1";
return;
continue;
}
if (Utilities.isValidIPV4(value)) {
ntClientModeServer = value;
return;
continue;
}
}
System.err.println("Argument for NT Server Host was invalid, defaulting to team number host");
@@ -97,8 +90,12 @@ public class Main {
case NETWORK_MANAGE_KEY:
manageNetwork = false;
break;
case IGNORE_ROOT:
case IGNORE_ROOT_KEY:
ignoreRoot = true;
break;
case TEST_MODE_KEY:
testMode = true;
break;
}
}
}
@@ -115,7 +112,6 @@ public class Main {
if (!CurrentPlatform.isRoot()) {
if (ignoreRoot) {
// TODO: should we do this?
// manageNetwork = false;
System.out.println("Ignoring root, network will not be managed!");
} else {
@@ -129,31 +125,42 @@ public class Main {
CameraServerJNI.forceLoad();
CameraServerCvJNI.forceLoad();
} catch (UnsatisfiedLinkError | IOException e) {
if(Platform.getCurrentPlatform().isWindows())
System.err.println("Try to download the VC++ Redistributable, see announcements in discord");
if(CurrentPlatform.isWindows()) {
System.err.println("Try to download the VC++ Redistributable, https://aka.ms/vs/16/release/vc_redist.x64.exe");
}
throw new RuntimeException("Failed to load JNI Libraries!");
}
if (CameraManager.initializeCameras()) {
SettingsManager.initialize();
NetworkManager.initialize(manageNetwork);
CameraManager.initializeThreads();
if (ntServerMode) {
System.out.println("Starting NT Server");
NetworkTableInstance.getDefault().startServer();
} else {
NetworkTableInstance.getDefault().addLogger(new NTLogger(), 0, 255); // to hide error messages
if (ntClientModeServer != null) {
NetworkTableInstance.getDefault().startClient(ntClientModeServer);
} else {
NetworkTableInstance.getDefault().startClientTeam(SettingsManager.GeneralSettings.teamNumber);
}
}
int webserverPort = DEFAULT_PORT;
System.out.printf("Starting Webserver at port %d\n", webserverPort);
Server.main(webserverPort);
ConfigManager.initializeSettings();
NetworkManager.initialize(manageNetwork);
if (ntServerMode) {
System.out.println("Starting NT Server");
NetworkTableInstance.getDefault().startServer();
} else {
System.err.println("No cameras connected!");
NetworkTableInstance.getDefault().addLogger(new NTLogger(), 0, 255); // to hide error messages
if (ntClientModeServer != null) {
NetworkTableInstance.getDefault().startClient(ntClientModeServer);
} else {
NetworkTableInstance.getDefault().startClientTeam(ConfigManager.settings.teamNumber);
}
}
boolean visionSourcesOk = VisionManager.initializeSources();
if (!visionSourcesOk) {
System.out.println("No cameras connected!");
return;
}
boolean visionProcessesOk = VisionManager.initializeProcesses();
if (!visionProcessesOk) {
System.err.println("shit");
return;
}
VisionManager.startProcesses();
System.out.printf("Starting Webserver at port %d\n", DEFAULT_PORT);
Server.main(DEFAULT_PORT);
}
}

View File

@@ -0,0 +1,171 @@
package com.chameleonvision.config;
import com.chameleonvision.util.JacksonHelper;
import com.chameleonvision.vision.pipeline.CVPipelineSettings;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CameraConfig {
private static final Path camerasConfigFolderPath = Paths.get(ConfigManager.SettingsPath.toString(), "cameras");
private final String cameraConfigName;
private final CameraJsonConfig preliminaryConfig;
CameraConfig(CameraJsonConfig config) {
preliminaryConfig = config;
cameraConfigName = preliminaryConfig.name.replace(' ', '_');
}
public CameraJsonConfig load() {
checkFolder();
checkConfig();
checkPipelines();
checkDriverMode();
return loadConfig();
}
private CameraJsonConfig loadConfig() {
CameraJsonConfig config = preliminaryConfig;
try {
config = JacksonHelper.deserializer(getConfigPath(), CameraJsonConfig.class);
} catch (IOException e) {
System.err.printf("Failed to load camera config: %s - using default.\n", getConfigPath().toString());
}
return config;
}
List<CVPipelineSettings> loadPipelines() {
List<CVPipelineSettings> pipelines = new ArrayList<>();
try {
var pipelineArray = JacksonHelper.deserializer(getPipelinesPath(), CVPipelineSettings[].class);
if (pipelineArray != null) {
pipelines = Arrays.asList(pipelineArray);
}
} catch (IOException e) {
System.err.println("Failed to load camera pipelines: " + getPipelinesPath().toString());
}
return pipelines;
}
CVPipelineSettings loadDriverMode() {
CVPipelineSettings driverMode = new CVPipelineSettings();
driverMode.nickname = "DRIVERMODE";
try {
driverMode = JacksonHelper.deserializer(getDriverModePath(), CVPipelineSettings.class);
} catch (IOException e) {
System.err.println("Failed to load camera drivermode: " + getDriverModePath().toString());
}
return driverMode;
}
void saveConfig(CameraJsonConfig config) {
try {
JacksonHelper.serializer(getConfigPath(), config);
} catch (IOException e) {
System.err.println("Failed to save camera config file: " + getConfigPath().toString());
}
}
void savePipelines(List<CVPipelineSettings> pipelines) {
try {
JacksonHelper.serializer(getPipelinesPath(), pipelines);
} catch (IOException e) {
System.err.println("Failed to save camera pipelines file: " + getConfigPath().toString());
}
}
void saveDriverMode(CVPipelineSettings driverMode) {
try {
JacksonHelper.serializer(getDriverModePath(), driverMode);
} catch (IOException e) {
System.err.println("Failed to save camera drivermode file: " + getDriverModePath().toString());
}
}
private void checkFolder() {
if (!folderExists()) {
try {
if (!(new File(getFolderPath().toUri()).mkdirs())) {
System.err.println("Failed to create camera config folder: " + getFolderPath().toString());
}
} catch(Exception e) {
if(!(e instanceof java.nio.file.FileAlreadyExistsException || e instanceof java.nio.file.FileAlreadyExistsException))
System.err.println("Failed to create camera config folder: " + getFolderPath().toString());
}
}
}
private void checkConfig() {
if (!configExists()) {
try {
JacksonHelper.serializer(getConfigPath(), preliminaryConfig);
} catch (IOException e) {
System.err.println("Failed to create camera config file: " + getConfigPath().toString());
}
}
}
private void checkPipelines() {
if (!pipelinesExists()) {
try {
Files.createFile(getPipelinesPath());
} catch (IOException e) {
System.err.println("Failed to create camera pipelines file: " + getPipelinesPath().toString());
}
}
}
private void checkDriverMode() {
if (!driverModeExists()) {
try {
CVPipelineSettings newDriverModeSettings = new CVPipelineSettings();
newDriverModeSettings.nickname = "DRIVERMODE";
JacksonHelper.serializer(getDriverModePath(), newDriverModeSettings);
} catch (IOException e) {
System.err.println("Failed to create camera drivermode file: " + getDriverModePath().toString());
}
}
}
private Path getFolderPath() {
return Paths.get(camerasConfigFolderPath.toString(), cameraConfigName);
}
private Path getConfigPath() {
return Paths.get(getFolderPath().toString(), "camera.json");
}
private Path getPipelinesPath() {
return Paths.get(getFolderPath().toString(), "pipelines.json");
}
private Path getDriverModePath() {
return Paths.get(getFolderPath().toString(), "drivermode.json");
}
private boolean folderExists() {
return Files.exists(getFolderPath());
}
private boolean configExists() {
return folderExists() && Files.exists(getConfigPath());
}
private boolean pipelinesExists() {
return folderExists() && Files.exists(getPipelinesPath());
}
private boolean driverModeExists() {
return folderExists() && Files.exists(getDriverModePath());
}
}

View File

@@ -0,0 +1,37 @@
package com.chameleonvision.config;
import com.chameleonvision.vision.camera.USBCameraCapture;
import com.chameleonvision.vision.camera.USBCameraProperties;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class CameraJsonConfig {
public final double fov;
public final String path;
public final String name;
public final String nickname;
@JsonCreator
public CameraJsonConfig(
@JsonProperty("fov") double fov,
@JsonProperty("path") String path,
@JsonProperty("name") String name,
@JsonProperty("nickname") String nickname) {
this.fov = fov;
this.path = path;
this.name = name;
this.nickname = nickname;
}
public CameraJsonConfig(String path, String name) {
this.fov = USBCameraProperties.DEFAULT_FOV;
this.path = path;
this.name = name;
this.nickname = name;
}
public static CameraJsonConfig fromUSBCameraProcess(USBCameraCapture process) {
USBCameraProperties camProps = process.getProperties();
return new CameraJsonConfig(camProps.FOV, camProps.name, camProps.path, camProps.getNickname());
}
}

View File

@@ -0,0 +1,113 @@
package com.chameleonvision.config;
import com.chameleonvision.util.ProgramDirectoryUtilities;
import com.chameleonvision.util.JacksonHelper;
import com.chameleonvision.vision.pipeline.CVPipelineSettings;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
public class ConfigManager {
private ConfigManager() {}
static final Path SettingsPath = Paths.get(ProgramDirectoryUtilities.getProgramDirectory(), "settings");
private static final Path settingsFilePath = Paths.get(SettingsPath.toString(), "settings.json");
private static final LinkedHashMap<String, CameraConfig> cameraConfigs = new LinkedHashMap<>();
public static GeneralSettings settings = new GeneralSettings();
private static boolean settingsFolderExists() { return Files.exists(SettingsPath); }
private static boolean settingsFileExists() { return settingsFolderExists() && Files.exists(settingsFilePath); }
private static void checkSettingsFolder() {
if (!settingsFolderExists()) {
try {
if( !(new File(SettingsPath.toUri()).mkdirs()) ) {
System.err.println("Failed to create settings folder: " + SettingsPath.toString());
}
Files.createDirectory(SettingsPath);
} catch (IOException e) {
if(!(e instanceof java.nio.file.FileAlreadyExistsException))
e.printStackTrace();
}
}
}
private static void checkSettingsFile() {
boolean settingsFileEmpty = settingsFileExists() && new File(settingsFilePath.toString()).length() == 0;
if (settingsFileEmpty || !settingsFileExists()) {
try {
JacksonHelper.serializer(settingsFilePath, settings);
} catch (IOException e) {
e.printStackTrace();
}
} else {
try {
settings = JacksonHelper.deserializer(settingsFilePath, GeneralSettings.class);
} catch (IOException e) {
System.err.println("Failed to load settings.json, using defaults.");
}
}
}
public static void initializeSettings() {
System.out.println("Settings folder: " + SettingsPath.toString());
checkSettingsFolder();
checkSettingsFile();
}
private static void saveSettingsFile() {
try {
JacksonHelper.serializer(settingsFilePath, settings);
} catch (IOException e) {
System.err.println("Failed to save settings.json!");
}
}
public static void saveGeneralSettings() {
checkSettingsFolder();
saveSettingsFile();
}
public static List<FullCameraConfiguration> initializeCameras(List<CameraJsonConfig> preliminaryConfigs) {
List<FullCameraConfiguration> configList = new ArrayList<>();
checkSettingsFolder();
// loop over all the camera names and try to create settings folders for it
for (CameraJsonConfig preliminaryConfig : preliminaryConfigs) {
CameraConfig cameraConfiguration = new CameraConfig(preliminaryConfig);
cameraConfigs.put(preliminaryConfig.name, cameraConfiguration);
CameraJsonConfig camJsonConfig = cameraConfiguration.load();
List<CVPipelineSettings> pipelines = cameraConfiguration.loadPipelines();
CVPipelineSettings driverMode = cameraConfiguration.loadDriverMode();
configList.add(new FullCameraConfiguration(camJsonConfig, pipelines, driverMode));
}
return configList;
}
public static void saveCameraConfig(String cameraName, CameraJsonConfig config) {
var camConf = cameraConfigs.get(cameraName);
camConf.saveConfig(config);
}
public static void saveCameraPipelines(String cameraName, List<CVPipelineSettings> pipelines) {
var camConf = cameraConfigs.get(cameraName);
camConf.savePipelines(pipelines);
}
public static void saveCameraDriverMode(String cameraName, CVPipelineSettings driverMode) {
var camConf = cameraConfigs.get(cameraName);
camConf.saveDriverMode(driverMode);
}
}

View File

@@ -0,0 +1,18 @@
package com.chameleonvision.config;
import com.chameleonvision.vision.pipeline.CVPipelineSettings;
import java.util.List;
public class FullCameraConfiguration {
public final CameraJsonConfig cameraConfig;
public final List<CVPipelineSettings> pipelines;
public final CVPipelineSettings drivermode;
public FullCameraConfiguration(CameraJsonConfig cameraConfig, List<CVPipelineSettings> pipelines, CVPipelineSettings drivermode) {
this.cameraConfig = cameraConfig;
this.pipelines = pipelines;
this.drivermode = drivermode;
}
}

View File

@@ -1,4 +1,4 @@
package com.chameleonvision.settings;
package com.chameleonvision.config;
import com.chameleonvision.network.NetworkIPMode;

View File

@@ -19,7 +19,7 @@ public class NetworkInterface {
IPAddress = inetAddress.getHostAddress();
Netmask = getIPv4LocalNetMask(ifaceAddress);
// TODO: hack to "get" gateway, this is gross and bad, pls fix
// TODO: (low) hack to "get" gateway, this is gross and bad, pls fix
var splitIPAddr = IPAddress.split("\\.");
splitIPAddr[3] = "1";
Gateway = String.join(".", splitIPAddr);

View File

@@ -1,8 +1,8 @@
package com.chameleonvision.network;
import com.chameleonvision.settings.Platform;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.config.ConfigManager;
import com.chameleonvision.util.Platform;
import java.net.SocketException;
import java.util.ArrayList;
@@ -43,7 +43,7 @@ public class NetworkManager {
e.printStackTrace();
}
var teamBytes = NetworkManager.GetTeamNumberIPBytes(SettingsManager.GeneralSettings.teamNumber);
var teamBytes = NetworkManager.GetTeamNumberIPBytes(ConfigManager.settings.teamNumber);
if (interfaces.size() > 0) {
for (var inetface : interfaces) {
@@ -85,7 +85,7 @@ public class NetworkManager {
return true;
}
var genSettings = SettingsManager.GeneralSettings;
var genSettings = ConfigManager.settings;
boolean isStatic = genSettings.connectionType.equals(NetworkIPMode.STATIC);
if (isStatic) {

View File

@@ -1,65 +0,0 @@
package com.chameleonvision.settings;
import com.chameleonvision.util.FileHelper;
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.settings.GeneralSettings GeneralSettings;
private SettingsManager() {}
public static void initialize() {
initGeneralSettings();
var allCameras = CameraManager.getAllCamerasByName();
if (!allCameras.containsKey(GeneralSettings.currentCamera) && allCameras.size() > 0) {
var cam = allCameras.entrySet().stream().findFirst().get().getValue();
GeneralSettings.currentCamera = cam.name;
GeneralSettings.currentPipeline = cam.getCurrentPipelineIndex();
}
}
private static void initGeneralSettings() {
FileHelper.CheckPath(SettingsPath);
try {
GeneralSettings = new Gson().fromJson(new FileReader(Paths.get(SettingsPath.toString(), "settings.json").toString()), com.chameleonvision.settings.GeneralSettings.class);
} catch (FileNotFoundException e) {
GeneralSettings = new GeneralSettings();
}
}
public static void updateCameraSetting(String cameraName, int pipelineNumber) {
GeneralSettings.currentCamera = cameraName;
GeneralSettings.currentPipeline = pipelineNumber;
}
public static void updatePipelineSetting(int pipelineNumber) {
GeneralSettings.currentPipeline = 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();
}
}
}

View File

@@ -1,25 +0,0 @@
package com.chameleonvision.util;
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();
}
}
}
}

View File

@@ -0,0 +1,18 @@
package com.chameleonvision.util;
import edu.wpi.cscore.VideoMode;
import org.opencv.core.Scalar;
import java.awt.*;
public class Helpers {
private Helpers() {}
public static Scalar colorToScalar(Color color) {
return new Scalar(color.getRed(), color.getGreen(), color.getBlue());
}
public static String VideoModeToString(VideoMode videoMode) {
return String.format("%dx%d@%dFPS in %s", videoMode.width, videoMode.height, videoMode.fps, videoMode.pixelFormat.toString());
}
}

View File

@@ -0,0 +1,30 @@
package com.chameleonvision.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class JacksonHelper {
private JacksonHelper() {} // no construction, utility class
public static void serializer(Path path, Object object) throws IOException {
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder().allowIfBaseType(Object.class).build();
ObjectMapper objectMapper = JsonMapper.builder().activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT).build();
objectMapper.writerWithDefaultPrettyPrinter().writeValue(new File(path.toString()), object);
}
public static <T> T deserializer(Path path, Class<T> ref) throws IOException {
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder().allowIfBaseType(ref).build();
ObjectMapper objectMapper = JsonMapper.builder().activateDefaultTyping(ptv).build();
File jsonFile = new File(path.toString());
if (jsonFile.exists() && jsonFile.length() > 0) {
return objectMapper.readValue(jsonFile, ref);
}
return null;
}
}

View File

@@ -0,0 +1,37 @@
package com.chameleonvision.util;
/**
* A thread that tries to run at a specified loop time
*/
public abstract class LoopingRunnable implements Runnable {
protected volatile Long loopTimeMs;
protected abstract void process();
public LoopingRunnable(Long loopTimeMs) {
this.loopTimeMs = loopTimeMs;
}
@Override
public void run() {
while(!Thread.interrupted()) {
var now = System.currentTimeMillis();
// Do the thing
process();
// sleep for the remaining time
var timeElapsed = System.currentTimeMillis() - now;
var delta = loopTimeMs - timeElapsed;
try {
if(delta > 0.0) {
Thread.sleep(delta, 0);
} else {
Thread.sleep(1);
}
} catch (Exception ignored) {}
}
}
}

View File

@@ -1,6 +1,4 @@
package com.chameleonvision.settings;
import com.chameleonvision.util.ShellExec;
package com.chameleonvision.util;
import java.io.BufferedReader;
import java.io.IOException;
@@ -23,6 +21,7 @@ public enum Platform {
private static final String OS_NAME = System.getProperty("os.name");
private static final String OS_ARCH = System.getProperty("os.arch");
public static final Platform CurrentPlatform = getCurrentPlatform();
public boolean isWindows() {
return this == WINDOWS_64;

View File

@@ -0,0 +1,52 @@
package com.chameleonvision.util;
import java.io.File;
import java.net.URISyntaxException;
public class ProgramDirectoryUtilities
{
private static String getJarName()
{
return new File(ProgramDirectoryUtilities.class.getProtectionDomain()
.getCodeSource()
.getLocation()
.getPath())
.getName();
}
private static boolean runningFromJAR()
{
String jarName = getJarName();
return jarName.contains(".jar");
}
public static String getProgramDirectory()
{
if (runningFromJAR())
{
return getCurrentJARDirectory();
} else
{
return System.getProperty("user.dir");
// return getCurrentProjectDirectory();
}
}
private static String getCurrentProjectDirectory()
{
return new File("").getAbsolutePath();
}
private static String getCurrentJARDirectory()
{
try
{
return new File(ProgramDirectoryUtilities.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParent();
} catch (URISyntaxException exception)
{
exception.printStackTrace();
}
return null;
}
}

View File

@@ -1,5 +0,0 @@
package com.chameleonvision.vision;
public enum Orientation {
Normal,Inverted//TODO add 90 and 270 deg rotation?
}

View File

@@ -1,28 +0,0 @@
package com.chameleonvision.vision;
import java.util.Arrays;
import java.util.List;
public class Pipeline {
public int exposure = 50;
public int brightness = 50;
public Orientation orientation = Orientation.Normal;
public List<Number> hue = Arrays.asList(50, 180);
public List<Number> saturation = Arrays.asList(50, 255);
public List<Number> value = Arrays.asList(50, 255);
public boolean erode = false;
public boolean dilate = false;
public List<Number> area = Arrays.asList(0.0, 100.0);
public List<Number> ratio = Arrays.asList(0.0, 20.0);
public List<Number> extent = Arrays.asList(0, 100);
public Number speckle = 5;
public boolean isBinary = false;
public SortMode sortMode = SortMode.Largest;
public TargetGroup targetGroup = TargetGroup.Single;
public TargetIntersection targetIntersection = TargetIntersection.Up;
public double m = 1;
public double b = 0;
public List<Number> point = Arrays.asList(0,0);
public CalibrationMode calibrationMode = CalibrationMode.None;
public String nickname;
}

View File

@@ -1,5 +0,0 @@
package com.chameleonvision.vision;
public enum TargetGroup {
Single,Dual
}

View File

@@ -0,0 +1,183 @@
package com.chameleonvision.vision;
import com.chameleonvision.config.CameraJsonConfig;
import com.chameleonvision.config.ConfigManager;
import com.chameleonvision.config.FullCameraConfiguration;
import com.chameleonvision.util.Helpers;
import com.chameleonvision.util.Platform;
import com.chameleonvision.vision.camera.CameraCapture;
import com.chameleonvision.vision.camera.USBCameraCapture;
import com.chameleonvision.vision.pipeline.CVPipeline;
import com.chameleonvision.vision.pipeline.CVPipelineSettings;
import edu.wpi.cscore.UsbCamera;
import edu.wpi.cscore.UsbCameraInfo;
import org.opencv.videoio.VideoCapture;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class VisionManager {
private VisionManager() {
}
private static final LinkedHashMap<String, UsbCameraInfo> usbCameraInfosByCameraName = new LinkedHashMap<>();
private static final LinkedList<FullCameraConfiguration> loadedCameraConfigs = new LinkedList<>();
private static final LinkedList<VisionProcessManageable> visionProcesses = new LinkedList<>();
private static class VisionProcessManageable {
public final int index;
public final String name;
public final VisionProcess visionProcess;
public VisionProcessManageable(int index, String name, VisionProcess visionProcess) {
this.index = index;
this.name = name;
this.visionProcess = visionProcess;
}
}
private static VisionProcess currentUIVisionProcess;
public static boolean initializeSources() {
int suffix = 0;
for (UsbCameraInfo info : UsbCamera.enumerateUsbCameras()) {
VideoCapture cap = new VideoCapture(info.dev);
if (cap.isOpened()) {
cap.release();
String name = info.name;
while (usbCameraInfosByCameraName.containsKey(name)) {
suffix++;
name = String.format("%s (%d)", name, suffix);
}
usbCameraInfosByCameraName.put(name, info);
}
}
if (usbCameraInfosByCameraName.isEmpty()) {
return false;
}
// load the config
List<CameraJsonConfig> preliminaryConfigs = new ArrayList<>();
usbCameraInfosByCameraName.values().forEach((cameraInfo) -> {
String truePath;
if (Platform.CurrentPlatform.isWindows()) {
truePath = cameraInfo.path;
} else {
truePath = Arrays.stream(cameraInfo.otherPaths).filter(x -> x.contains("/dev/v4l/by-path")).findFirst().orElse(cameraInfo.path);
}
preliminaryConfigs.add(new CameraJsonConfig(truePath, cameraInfo.name));
});
loadedCameraConfigs.addAll(ConfigManager.initializeCameras(preliminaryConfigs));
// TODO: (HIGH) Load pipelines from json
// UsbCameraInfosByCameraName.forEach((cameraName, cameraInfo) -> {
// Path cameraConfigFolder = Paths.get(CamConfigPath.toString(), String.format("%s\\", cameraName));
// Path cameraConfigPath = Paths.get(cameraConfigFolder.toString(), String.format("%s.json", cameraName));
// Path cameraPipelinesPath = Paths.get(cameraConfigFolder.toString(), "pipelines.json");
// Path cameraDrivermodePath = Paths.get(cameraConfigFolder.toString(), "drivermode.json");
return true;
}
public static boolean initializeProcesses() {
for (int i = 0; i < loadedCameraConfigs.size(); i++) {
FullCameraConfiguration config = loadedCameraConfigs.get(i);
CameraJsonConfig cameraJsonConfig = config.cameraConfig;
CameraCapture camera = new USBCameraCapture(cameraJsonConfig);
VisionProcess process = new VisionProcess(camera, cameraJsonConfig.name);
config.pipelines.forEach(process::addPipeline);
process.setDriverModeSettings(config.drivermode);
visionProcesses.add(new VisionProcessManageable(i, cameraJsonConfig.name, process));
}
currentUIVisionProcess = getVisionProcessByIndex(0);
ConfigManager.settings.currentCamera = visionProcesses.get(0).name;
return true;
}
public static void startProcesses() {
visionProcesses.forEach((vpm) -> {
vpm.visionProcess.start();
});
}
public static VisionProcess getCurrentUIVisionProcess() {
return currentUIVisionProcess;
}
public static void setCurrentProcessByIndex(int processIndex) {
if (processIndex > visionProcesses.size() - 1) {
return;
}
currentUIVisionProcess = getVisionProcessByIndex(processIndex);
ConfigManager.settings.currentCamera = visionProcesses.get(processIndex).name;
}
public static VisionProcess getVisionProcessByIndex(int processIndex) {
if (processIndex > visionProcesses.size() - 1) {
return null;
}
VisionProcessManageable vpm = visionProcesses.stream().filter(manageable -> manageable.index == processIndex).findFirst().orElse(null);
return vpm != null ? vpm.visionProcess : null;
}
public static List<String> getAllCameraNicknames() {
return visionProcesses.stream().map(vpm -> vpm.visionProcess.getCamera()
.getProperties().getNickname()).collect(Collectors.toList());
}
public static List<String> getCurrentCameraPipelineNicknames() {
return currentUIVisionProcess.getPipelines().stream().map(cvPipeline -> cvPipeline.settings.nickname).collect(Collectors.toList());
}
public static void saveAllCameras() {
visionProcesses.forEach((vpm) -> {
VisionProcess process = vpm.visionProcess;
String cameraName = process.getCamera().getProperties().name;
List<CVPipelineSettings> pipelines = process.getPipelines().stream().map(cvPipeline -> cvPipeline.settings).collect(Collectors.toList());
CVPipelineSettings driverMode = process.getDriverModeSettings();
CameraJsonConfig config = CameraJsonConfig.fromUSBCameraProcess((USBCameraCapture) process.getCamera());
ConfigManager.saveCameraPipelines(cameraName, pipelines);
ConfigManager.saveCameraDriverMode(cameraName, driverMode);
ConfigManager.saveCameraConfig(cameraName, config);
});
}
private static String getCurrentCameraName() {
return currentUIVisionProcess.getCamera().getProperties().name;
}
public static void saveCurrentCameraSettings() {
CameraJsonConfig config = CameraJsonConfig.fromUSBCameraProcess((USBCameraCapture) currentUIVisionProcess.getCamera());
ConfigManager.saveCameraConfig(getCurrentCameraName(), config);
}
public static void saveCurrentCameraPipelines() {
List<CVPipelineSettings> pipelineSettings = currentUIVisionProcess.getPipelines().stream().map(pipeline -> pipeline.settings).collect(Collectors.toList());
ConfigManager.saveCameraPipelines(getCurrentCameraName(), pipelineSettings);
}
public static void saveCurrentCameraDriverMode() {
CVPipelineSettings driverModeSettings = currentUIVisionProcess.getDriverModeSettings();
ConfigManager.saveCameraDriverMode(getCurrentCameraName(), driverModeSettings);
}
public static List<String> getCurrentCameraResolutionList() {
return currentUIVisionProcess.getCamera().getProperties().getVideoModes().stream().map(Helpers::VideoModeToString).collect(Collectors.toList());
}
public static int getCurrentUIVisionProcessIndex() {
VisionProcessManageable vpm = visionProcesses.stream().filter(v -> v.visionProcess == currentUIVisionProcess).findFirst().orElse(null);
return vpm != null ? vpm.index : -1;
}
}

View File

@@ -0,0 +1,353 @@
package com.chameleonvision.vision;
import com.chameleonvision.Debug;
import com.chameleonvision.Main;
import com.chameleonvision.config.ConfigManager;
import com.chameleonvision.util.LoopingRunnable;
import com.chameleonvision.vision.camera.CameraCapture;
import com.chameleonvision.vision.camera.CameraStreamer;
import com.chameleonvision.vision.pipeline.*;
import com.chameleonvision.web.ServerHandler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.wpi.cscore.VideoMode;
import edu.wpi.first.networktables.*;
import edu.wpi.first.wpiutil.CircularBuffer;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Mat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
public class VisionProcess {
private final CameraCapture cameraCapture;
private final List<CVPipeline> pipelines = new ArrayList<>();
private final CameraStreamerRunnable streamRunnable;
private final VisionProcessRunnable visionRunnable;
public final CameraStreamer cameraStreamer;
private CVPipeline currentPipeline;
private int currentPipelineIndex = 0;
private CVPipeline driverModePipeline = new DriverVisionPipeline(new CVPipelineSettings());
private volatile CVPipelineResult lastPipelineResult;
private BlockingQueue<Mat> streamFrameQueue = new LinkedBlockingDeque<>(1);
// network table stuff
private final NetworkTable defaultTable;
private NetworkTableEntry ntPipelineEntry;
private NetworkTableEntry ntDriverModeEntry;
private int ntDriveModeListenerID;
private int ntPipelineListenerID;
private NetworkTableEntry ntYawEntry;
private NetworkTableEntry ntPitchEntry;
private NetworkTableEntry ntAuxListEntry;
private NetworkTableEntry ntAreaEntry;
private NetworkTableEntry ntTimeStampEntry;
private NetworkTableEntry ntValidEntry;
private ObjectMapper objectMapper = new ObjectMapper();
VisionProcess(CameraCapture cameraCapture, String name) {
this.cameraCapture = cameraCapture;
pipelines.add(new CVPipeline2d("New Pipeline"));
setPipeline(0, false);
// Thread to put frames on the dashboard
this.cameraStreamer = new CameraStreamer(cameraCapture, name);
this.streamRunnable = new CameraStreamerRunnable(30, cameraStreamer);
// Thread to process vision data
this.visionRunnable = new VisionProcessRunnable();
// network table
defaultTable = NetworkTableInstance.getDefault().getTable("/chameleon-vision/" + cameraCapture.getProperties().name);
}
public void start() {
System.out.println("Starting NetworkTables.");
initNT(defaultTable);
System.out.println("Starting vision thread.");
new Thread(visionRunnable).start();
System.out.println("Starting stream thread.");
new Thread(streamRunnable).start();
}
/**
* Removes the old value change listeners
* calls {@link #initNT}
*
* @param newTable passed to {@link #initNT}
*/
public void resetNT(NetworkTable newTable) {
ntDriverModeEntry.removeListener(ntDriveModeListenerID);
ntPipelineEntry.removeListener(ntPipelineListenerID);
initNT(newTable);
}
private void initNT(NetworkTable newTable) {
ntPipelineEntry = newTable.getEntry("pipeline");
ntDriverModeEntry = newTable.getEntry("driver_mode");
ntPitchEntry = newTable.getEntry("pitch");
ntYawEntry = newTable.getEntry("yaw");
ntAreaEntry = newTable.getEntry("area");
ntTimeStampEntry = newTable.getEntry("timestamp");
ntValidEntry = newTable.getEntry("is_valid");
ntAuxListEntry = newTable.getEntry("aux_targets");
ntDriveModeListenerID = ntDriverModeEntry.addListener(this::setDriverMode, EntryListenerFlags.kUpdate);
ntPipelineListenerID = ntPipelineEntry.addListener(this::setPipeline, EntryListenerFlags.kUpdate);
ntDriverModeEntry.setBoolean(false);
ntPipelineEntry.setNumber(0);
}
private void setDriverMode(EntryNotification driverModeEntryNotification) {
setDriverMode(driverModeEntryNotification.value.getBoolean());
}
public void setDriverMode(boolean driverMode) {
if (driverMode) {
setPipelineInternal(driverModePipeline);
} else {
setPipeline(currentPipelineIndex, true);
}
}
/**
* Method called by the nt entry listener to update the next pipeline.
* @param notification the notification
*/
private void setPipeline(EntryNotification notification) {
var wantedPipelineIndex = (int) notification.value.getDouble();
if (wantedPipelineIndex >= pipelines.size()) {
ntPipelineEntry.setNumber(currentPipelineIndex);
} else {
currentPipelineIndex = wantedPipelineIndex;
setPipeline(wantedPipelineIndex, true);
}
}
public void setPipeline(int pipelineIndex, boolean updateUI) {
CVPipeline newPipeline = pipelines.get(pipelineIndex);
if (newPipeline != null) {
setPipelineInternal(newPipeline);
currentPipelineIndex = pipelineIndex;
// update the configManager
if(ConfigManager.settings.currentCamera.equals(cameraCapture.getProperties().name)) {
ConfigManager.settings.currentPipeline = pipelineIndex;
if (updateUI) {
HashMap<String, Object> pipeChange = new HashMap<>();
pipeChange.put("currentPipeline", pipelineIndex);
ServerHandler.broadcastMessage(pipeChange);
ServerHandler.sendFullSettings();
}
}
}
}
private void setPipelineInternal(CVPipeline pipeline) {
currentPipeline = pipeline;
currentPipeline.initPipeline(cameraCapture);
}
private void updateUI(CVPipelineResult data) {
if(cameraCapture.getProperties().name.equals(ConfigManager.settings.currentCamera)) {
HashMap<String, Object> WebSend = new HashMap<>();
HashMap<String, Object> point = new HashMap<>();
HashMap<String, Object> calculated = new HashMap<>();
List<Double> center = new ArrayList<>();
if (data.hasTarget) {
if(data instanceof CVPipeline2d.CVPipeline2dResult) {
CVPipeline2d.CVPipeline2dResult result = (CVPipeline2d.CVPipeline2dResult) data;
CVPipeline2d.Target2d bestTarget = result.targets.get(0);
center.add(bestTarget.rawPoint.center.x);
center.add(bestTarget.rawPoint.center.y);
calculated.put("pitch", bestTarget.pitch);
calculated.put("yaw", bestTarget.yaw);
calculated.put("area", bestTarget.area);
} else if (data instanceof CVPipeline3d.CVPipeline3dResult) {
// TODO: (2.1) 3d stuff in UI
} else {
center.add(0.0);
center.add(0.0);
calculated.put("pitch", 0);
calculated.put("yaw", 0);
}
} else {
center.add(0.0);
center.add(0.0);
calculated.put("pitch", 0);
calculated.put("yaw", 0);
}
point.put("fps", visionRunnable.fps);
point.put("calculated", calculated);
point.put("rawPoint", center);
WebSend.put("point", point);
ServerHandler.broadcastMessage(WebSend);
}
}
private void updateNetworkTableData(CVPipelineResult data) {
ntValidEntry.setBoolean(data.hasTarget);
if(data.hasTarget && !(data instanceof DriverVisionPipeline.DriverPipelineResult)) {
if(data instanceof CVPipeline2d.CVPipeline2dResult) {
//noinspection unchecked
List<CVPipeline2d.Target2d> targets = (List<CVPipeline2d.Target2d>) data.targets;
ntTimeStampEntry.setDouble(data.imageTimestamp);
ntPitchEntry.setDouble(targets.get(0).pitch);
ntYawEntry.setDouble(targets.get(0).yaw);
ntAreaEntry.setDouble(targets.get(0).area);
try {
ntAreaEntry.setString(objectMapper.writeValueAsString(targets));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
} else if (data instanceof CVPipeline3d.CVPipeline3dResult) {
// TODO: (2.1) 3d stuff...
}
} else {
ntPitchEntry.setDouble(0.0);
ntYawEntry.setDouble(0.0);
ntAreaEntry.setDouble(0.0);
ntTimeStampEntry.setDouble(0.0);
ntAuxListEntry.setString("");
}
}
public void setVideoMode(VideoMode newMode) {
cameraCapture.setVideoMode(newMode);
cameraStreamer.setNewVideoMode(newMode);
}
public List<CVPipeline> getPipelines() {
return pipelines;
}
public CVPipeline getCurrentPipeline() {
return currentPipeline;
}
public int getCurrentPipelineIndex() {
return currentPipelineIndex;
}
public void addPipeline() {
// TODO: (2.1) add to UI option between 2d and 3d pipeline
pipelines.add(new CVPipeline2d());
}
public void addPipeline(CVPipeline pipeline) {
pipelines.add(pipeline);
}
public void addPipeline(CVPipelineSettings settings) {
if (settings instanceof CVPipeline2dSettings) {
pipelines.add(new CVPipeline2d((CVPipeline2dSettings) settings));
}
}
public CameraCapture getCamera() {
return cameraCapture;
}
public boolean getDriverMode() {
return (currentPipeline == driverModePipeline);
}
public void setDriverModeSettings(CVPipelineSettings settings) {
driverModePipeline.settings = settings;
}
public CVPipelineSettings getDriverModeSettings() {
return driverModePipeline.settings;
}
public CVPipeline getPipelineByIndex(int pipelineIndex) {
return pipelines.get(pipelineIndex);
}
/**
* VisionProcessRunnable will process images as quickly as possible
*/
private class VisionProcessRunnable implements Runnable {
volatile Double fps = 0.0;
private CircularBuffer fpsAveragingBuffer = new CircularBuffer(7);
@Override
public void run() {
var lastUpdateTimeNanos = System.nanoTime();
while(!Thread.interrupted()) {
// blocking call, will block until camera has a new frame.
Pair<Mat, Long> camData = cameraCapture.getFrame();
Mat camFrame = camData.getLeft();
if (camFrame.cols() > 0 && camFrame.rows() > 0) {
CVPipelineResult result = currentPipeline.runPipeline(camFrame);
if (result != null) {
result.setTimestamp(camData.getRight());
lastPipelineResult = result;
updateNetworkTableData(lastPipelineResult);
updateUI(lastPipelineResult);
}
}
try {
streamFrameQueue.clear();
streamFrameQueue.add(lastPipelineResult.outputMat);
} catch (Exception e) {
Debug.printInfo("Vision running faster than stream.");
}
var deltaTimeNanos = lastUpdateTimeNanos - System.nanoTime();
fpsAveragingBuffer.addFirst(1.0 / (deltaTimeNanos * 1E-09));
lastUpdateTimeNanos = System.nanoTime();
fps = getAverageFPS();
}
}
double getAverageFPS() {
var temp = 0.0;
for(int i = 0; i < 7; i++) {
temp += fpsAveragingBuffer.get(i);
}
temp /= 7.0;
return temp;
}
}
private class CameraStreamerRunnable extends LoopingRunnable {
final CameraStreamer streamer;
private CameraStreamerRunnable(int cameraFPS, CameraStreamer streamer) {
// add 2 FPS to allow for a bit of overhead
super(1000L/(cameraFPS + 2));
this.streamer = streamer;
}
@Override
protected void process() {
if (!streamFrameQueue.isEmpty()) {
try {
streamer.runStream(streamFrameQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

View File

@@ -1,53 +0,0 @@
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;
}
}
}

View File

@@ -1,355 +0,0 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.settings.Platform;
import com.chameleonvision.vision.Pipeline;
import com.chameleonvision.web.SocketHandler;
import edu.wpi.cscore.*;
import edu.wpi.first.cameraserver.CameraServer;
import edu.wpi.first.networktables.NetworkTable;
import edu.wpi.first.networktables.NetworkTableInstance;
import org.opencv.core.Mat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Camera {
private static final double DEFAULT_FOV = 60.8;
private static final StreamDivisor DEFAULT_STREAMDIVISOR = StreamDivisor.none;
public static final int DEFAULT_EXPOSURE = 50;
public static final int DEFAULT_BRIGHTNESS = 50;
private static final int MINIMUM_FPS = 30;
private static final int MINIMUM_WIDTH = 320;
private static final int MINIMUM_HEIGHT = 200;
private static final int MAX_INIT_MS = 1500;
private static final List<VideoMode.PixelFormat> ALLOWED_PIXEL_FORMATS = Arrays.asList(VideoMode.PixelFormat.kYUYV, VideoMode.PixelFormat.kMJPEG);
public final String name;
public final String path;
private String nickname;
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 StreamDivisor streamDivisor;
private CameraValues camVals;
private CamVideoMode camVideoMode;
private int currentPipelineIndex;
private List<Pipeline> pipelines;
//Driver mode camera settings
private int driverExposure;
private int driverBrightness;
private boolean isDriver;
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 usbCameraInfo, double fov) {
this(cameraName, usbCameraInfo, fov, DEFAULT_STREAMDIVISOR);
}
public Camera(String cameraName, UsbCameraInfo usbCamInfo, double fov, StreamDivisor divisor) {
this(cameraName, usbCamInfo, fov, new ArrayList<>(), 0, divisor, false);
}
public Camera(String cameraName, double fov, List<Pipeline> pipelines, int videoModeIndex, StreamDivisor divisor, boolean isDriver) {
this(cameraName, CameraManager.AllUsbCameraInfosByName.get(cameraName), fov, pipelines, videoModeIndex, divisor, isDriver);
}
public Camera(String cameraName, double fov, int videoModeIndex, StreamDivisor divisor, boolean isDriver) {
this(cameraName, fov, new ArrayList<>(), videoModeIndex, divisor, isDriver);
}
public Camera(String cameraName, UsbCameraInfo usbCamInfo, double fov, List<Pipeline> pipelines, int videoModeIndex, StreamDivisor divisor, boolean isDriver) {
FOV = fov;
name = cameraName;
if (Platform.getCurrentPlatform().isWindows()) {
path = usbCamInfo.path;
} else {
var truePath = Arrays.stream(usbCamInfo.otherPaths).filter(x -> x.contains("/dev/v4l/by-path")).findFirst();
path = truePath.orElse(null);
}
streamDivisor = divisor;
UsbCam = new UsbCamera(name, path);
this.pipelines = pipelines;
// set up video modes according to minimums
if (Platform.getCurrentPlatform() == Platform.WINDOWS_64 && !UsbCam.isConnected()) {
System.out.print("Waiting on camera... ");
long initTimeout = System.nanoTime();
while (!UsbCam.isConnected()) {
if (((System.nanoTime() - initTimeout) / 1e6) >= MAX_INIT_MS) {
break;
}
}
var initTimeMs = (System.nanoTime() - initTimeout) / 1e6;
System.out.printf("Camera initialized in %.2fms\n", initTimeMs);
}
var trueVideoModes = UsbCam.enumerateVideoModes();
availableVideoModes = Arrays.stream(trueVideoModes).filter(v ->
v.fps >= MINIMUM_FPS && v.width >= MINIMUM_WIDTH && v.height >= MINIMUM_HEIGHT && ALLOWED_PIXEL_FORMATS.contains(v.pixelFormat)).toArray(VideoMode[]::new);
if (availableVideoModes.length == 0) {
System.err.println("Camera not supported!");
throw new RuntimeException(new CameraException(CameraException.CameraExceptionType.BAD_CAMERA));
}
if (videoModeIndex <= availableVideoModes.length - 1) {
setCamVideoMode(videoModeIndex, false);
} else {
setCamVideoMode(0, false);
}
cvSink = cs.getVideo(UsbCam);
cvSource = cs.putVideo(name, camVals.ImageWidth / streamDivisor.value , camVals.ImageHeight / streamDivisor.value);
}
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;
// update camera values
camVals = new CameraValues(this);
boolean hasPrevVideoMode = prevVideoMode != null;
boolean newVideoModeIsNew = hasPrevVideoMode && !prevVideoMode.equals(newVideoMode);
if (newVideoModeIsNew || !hasPrevVideoMode) {
UsbCam.setVideoMode(newVideoMode.getActualPixelFormat(), newVideoMode.width, newVideoMode.height, newVideoMode.fps);
if (updateCvSource) {
updateCvSource();
}
}
}
private void updateCvSource() {
CameraManager.getVisionProcessByCameraName(name).cameraProcess.updateFrameSize();
synchronized (cvSourceLock) {
var newWidth = camVideoMode.width / streamDivisor.value;
var newHeight = camVideoMode.height / streamDivisor.value;
cvSource = cs.putVideo(name, newWidth, newHeight);
}
SocketHandler.sendFullSettings();
}
public void addPipeline() {
Pipeline p = new Pipeline();
p.nickname = "New pipeline " + pipelines.size();
addPipeline(p);
}
public void addPipeline(Pipeline p) {
this.pipelines.add(p);
}
public void deletePipeline(int index) {
pipelines.remove(index);
}
public void deletePipeline() {
deletePipeline(getCurrentPipelineIndex());
}
public Pipeline getCurrentPipeline() {
return getPipelineByIndex(currentPipelineIndex);
}
public Pipeline getPipelineByIndex(int pipelineIndex) {
return pipelines.get(pipelineIndex);
}
public int getCurrentPipelineIndex() {
return currentPipelineIndex;
}
public void setCurrentPipelineIndex(int pipelineNumber) {
if (pipelineNumber - 1 > pipelines.size()) return;
currentPipelineIndex = pipelineNumber;
}
public StreamDivisor getStreamDivisor() {
return streamDivisor;
}
public void setStreamDivisor(int divisor, boolean updateCvSource) {
streamDivisor = StreamDivisor.values()[divisor];
if (updateCvSource) {
updateCvSource();
}
}
public List<Pipeline> getPipelines() {
return pipelines;
}
public List<String> getPipelinesNickname() {
var pipelines = getPipelines();
return pipelines.stream().map(pipeline -> pipeline.nickname).collect(Collectors.toList());
}
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(Number fov) {
FOV = fov.doubleValue();
camVals = new CameraValues(this);
}
public void setDriverMode(boolean state)
{
isDriver = state;
if( isDriver ) {
setBrightness(driverBrightness); //We call setBrightness because it updates after 2 calls
setBrightness(driverBrightness); //Check it after we update to 2020 libraries
setExposure(driverExposure);
}
else{
UsbCam.setBrightness(getCurrentPipeline().brightness);
UsbCam.setBrightness(getCurrentPipeline().brightness);
try{UsbCam.setExposureManual(getCurrentPipeline().exposure);}
catch (VideoException e)
{
System.out.println("Exposure change isn't supported");
}
}
}
public boolean getDriverMode()
{
return isDriver;
}
public int getBrightness() {
return UsbCam.getBrightness();
}
public void setBrightness(int brightness) {
if (isDriver) {
driverBrightness = brightness;
UsbCam.setBrightness(brightness); // set twice to reduce timeout
}
else {
getCurrentPipeline().brightness = brightness;
}
UsbCam.setBrightness(brightness);
}
public void setExposure(int exposure) {
if (isDriver) {
driverExposure = exposure;
}
else {
getCurrentPipeline().exposure = exposure;
}
try {
UsbCam.setExposureManual(exposure);
} catch (VideoException e) {
System.err.println("Camera Does not support exposure change");
}
}
public long grabFrame(Mat image) {
return cvSink.grabFrame(image);
}
public CameraValues getCamVals() {
return camVals;
}
public void putFrame(Mat image) {
synchronized (cvSourceLock) {
cvSource.putFrame(image);
}
}
public List<HashMap> getResolutionList() {
return Arrays.stream(availableVideoModes)
.map(res -> new HashMap<String,Object>(){{
put("width", res.width);
put("height", res.height);
put("fps", res.fps);
put("pixelFormat", res.pixelFormat);
}})
.collect(Collectors.toList());
}
public void setNickname(String newNickname) {
//Deletes old camera nt table
NetworkTableInstance.getDefault().getTable("/chameleon-vision/" + this.nickname).getInstance().deleteAllEntries();
nickname = newNickname;
if (CameraManager.AllVisionProcessesByName.containsKey(this.name)) {
NetworkTable newNT = NetworkTableInstance.getDefault().getTable("/chameleon-vision/" + this.nickname);
CameraManager.AllVisionProcessesByName.get(this.name).resetNT(newNT);
}
}
public String getNickname() {
return nickname == null ? name : nickname;
}
public void setDriverExposure(int exposure) {
driverExposure = exposure;
if (isDriver) {
setExposure(exposure);
}
}
public void setDriverBrightness(int brightness) {
driverBrightness = brightness;
if (isDriver) {
setBrightness(brightness);
}
}
public int getDriverExposure() {
return driverExposure;
}
public int getDriverBrightness() {
return driverBrightness;
}
}

View File

@@ -0,0 +1,33 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.vision.image.ImageCapture;
import edu.wpi.cscore.VideoMode;
public interface CameraCapture extends ImageCapture {
USBCameraProperties getProperties();
/**
* Set the exposure of the camera
* @param exposure the new exposure to set the camera to
*/
void setExposure(int exposure);
/**
* Set the brightness of the camera
* @param brightness the new brightness to set the camera to
*/
void setBrightness(int brightness);
/**
* Set the video mode (fps and resolution) of the camera
* @param mode the wanted mode
*/
void setVideoMode(VideoMode mode);
/**
* Set the gain of the camera
* NOTE - Not all cameras support this.
* @param gain the new gain to set the camera to
*/
void setGain(int gain);
}

View File

@@ -1,64 +0,0 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.vision.Pipeline;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.ArrayType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.gson.*;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class CameraDeserializer implements JsonDeserializer<Camera> {
@Override
public Camera deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) throws JsonParseException {
try {
var jsonObj = jsonElement.getAsJsonObject();
var camFOV = jsonObj.get("FOV").getAsDouble();
var camName = jsonObj.get("name").getAsString();
var camNickname = jsonObj.get("nickname").getAsString();
var videoModeIndex = jsonObj.get("resolution").getAsInt();
// new for 2.0
var isDriverObj = jsonObj.get("isDriver");
var driverExposureObj = jsonObj.get("driverExposure");
var driverBrightnessObj = jsonObj.get("driverBrightness");
var divisorObj = jsonObj.get("streamDivisor");
// always null-check new features
boolean isDriver = isDriverObj != null && isDriverObj.getAsBoolean();
int driverExposure = driverExposureObj == null ? Camera.DEFAULT_EXPOSURE : driverExposureObj.getAsInt();
int driverBrightness = driverBrightnessObj == null ? Camera.DEFAULT_BRIGHTNESS : driverBrightnessObj.getAsInt();
StreamDivisor divisor = divisorObj == null ? StreamDivisor.none : StreamDivisor.values()[divisorObj.getAsInt()];
var pipelines = jsonObj.get("pipelines");
List<Pipeline> actualPipelines = new ArrayList<>();
ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory = mapper.getTypeFactory();
JavaType arrayType = typeFactory.constructCollectionType(List.class, Pipeline.class);
try {
actualPipelines = mapper.readValue(pipelines.toString(), arrayType);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
var newCamera = actualPipelines != null ? new Camera(camName, camFOV, actualPipelines, videoModeIndex, divisor, isDriver) : new Camera(camName, camFOV, videoModeIndex, divisor, isDriver);
newCamera.setNickname(camNickname != null ? camNickname : "");
newCamera.setDriverExposure(driverExposure);
newCamera.setDriverBrightness(driverBrightness);
return newCamera;
}
catch (NullPointerException e)
{
System.err.println("Error while reading json, value doesn't exist!");
System.err.println("Try to delete the camera settings in settings/cameras/YOURCAMERA.json");
e.printStackTrace();
return null;
}
}
}

View File

@@ -1,25 +0,0 @@
package com.chameleonvision.vision.camera;
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!";
}
}
}
CameraException(CameraExceptionType camExceptionType) {
super(camExceptionType.toString());
}
}

View File

@@ -1,162 +0,0 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.settings.GeneralSettings;
import com.chameleonvision.util.FileHelper;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.vision.Pipeline;
import com.chameleonvision.vision.process.VisionProcess;
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.*;
import java.util.stream.Collectors;
public class CameraManager {
private static final Path CamConfigPath = Paths.get(SettingsManager.SettingsPath.toString(), "cameras");
private static LinkedHashMap<String, Camera> AllCamerasByName = new LinkedHashMap<>();
public static HashMap<String, VisionProcess> AllVisionProcessesByName = 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 List<String> getAllCameraByNickname(){
var cameras = getAllCamerasByName();
return cameras.values().stream().map(Camera::getNickname).collect(Collectors.toList());
}
public static boolean initializeCameras() {
if (AllUsbCameraInfosByName.size() == 0) return false;
FileHelper.CheckPath(CamConfigPath);
AllUsbCameraInfosByName.forEach((key, value) -> {
var camPath = Paths.get(CamConfigPath.toString(), String.format("%s.json", key));
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(key, gsonRead);
} catch (FileNotFoundException ex) {
ex.printStackTrace();
}
} else {
if (!addCamera(new Camera(key), key)) {
System.err.println("Failed to add camera! Already exists!");
}
}
});
return true;
}
public static void initializeThreads(){
AllCamerasByName.forEach((key, value) -> {
VisionProcess visionProcess = new VisionProcess(value);
AllVisionProcessesByName.put(key, visionProcess);
new Thread(visionProcess).start();
});
}
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 getCameraByIndex(int index) {
return AllCamerasByName.get( (AllCamerasByName.keySet().toArray())[ index ] );
}
public static Camera getCurrentCamera() throws CameraException {
if (AllCamerasByName.size() == 0) throw new CameraException(CameraException.CameraExceptionType.NO_CAMERA);
var curCam = AllCamerasByName.get(SettingsManager.GeneralSettings.currentCamera);
if (curCam == null) throw new CameraException(CameraException.CameraExceptionType.BAD_CAMERA);
return curCam;
}
public static Integer getCurrentCameraIndex() throws CameraException {
if (AllCamerasByName.size() == 0) throw new CameraException(CameraException.CameraExceptionType.NO_CAMERA);
List<String> arr = new ArrayList<>(AllCamerasByName.keySet());
for (var i = 0; i < AllCamerasByName.size(); i++){
if (SettingsManager.GeneralSettings.currentCamera.equals(arr.get(i))){
return i;
}
}
return null;
}
public static void setCurrentCamera(String cameraName) throws CameraException {
if (!AllCamerasByName.containsKey(cameraName))
throw new CameraException(CameraException.CameraExceptionType.BAD_CAMERA);
SettingsManager.GeneralSettings.currentCamera = cameraName;
SettingsManager.updateCameraSetting(cameraName, getCurrentCamera().getCurrentPipelineIndex());
}
public static void setCurrentCamera(int cameraIndex) throws CameraException {
List<String> s = new ArrayList<String>(AllCamerasByName.keySet());
setCurrentCamera(s.get(cameraIndex));
}
public static Pipeline getCurrentPipeline() throws CameraException {
return getCurrentCamera().getCurrentPipeline();
}
public static void setCurrentPipeline(int pipelineNumber) throws CameraException {
if (pipelineNumber >= getCurrentCamera().getPipelines().size()){
throw new CameraException(CameraException.CameraExceptionType.BAD_PIPELINE);
}
getCurrentCamera().setCurrentPipelineIndex(pipelineNumber);
SettingsManager.updatePipelineSetting(pipelineNumber);
}
public static VisionProcess getVisionProcessByCameraName(String cameraName) {
return AllVisionProcessesByName.get(cameraName);
}
public static VisionProcess getCurrentVisionProcess() throws CameraException {
if (!SettingsManager.GeneralSettings.currentCamera.equals("")){
return AllVisionProcessesByName.get(SettingsManager.GeneralSettings.currentCamera);
}
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();
}
}
}
}

View File

@@ -1,24 +0,0 @@
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);
obj.addProperty("nickname", camera.getNickname());
obj.addProperty("streamDivisor", camera.getStreamDivisor().ordinal());
var pipelines = context.serialize(camera.getPipelines());
obj.add("pipelines", pipelines);
obj.addProperty("resolution", camera.getVideoModeIndex());
// obj.add("camVideoMode", context.serialize(camera.getVideoMode()));
obj.add("isDriver",context.serialize(camera.getDriverMode()));
obj.add("driverExposure",context.serialize(camera.getDriverExposure()));
obj.add("driverBrightness",context.serialize(camera.getDriverBrightness()));
return obj;
}
}

View File

@@ -0,0 +1,73 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.vision.enums.StreamDivisor;
import com.chameleonvision.web.ServerHandler;
import edu.wpi.cscore.CvSource;
import edu.wpi.cscore.MjpegServer;
import edu.wpi.cscore.VideoMode;
import edu.wpi.first.cameraserver.CameraServer;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
public class CameraStreamer {
private final CameraCapture cameraCapture;
private final String name;
private StreamDivisor divisor = StreamDivisor.NONE;
private CvSource cvSource;
private final Object streamBufferLock = new Object();
private Mat streamBuffer = new Mat();
public CameraStreamer(CameraCapture cameraCapture, String name) {
this.cameraCapture = cameraCapture;
this.name = name;
this.cvSource = CameraServer.getInstance().putVideo(name,
cameraCapture.getProperties().getStaticProperties().imageWidth / divisor.value,
cameraCapture.getProperties().getStaticProperties().imageHeight / divisor.value);
setDivisor(divisor, false);
}
public void setDivisor(StreamDivisor newDivisor, boolean updateUI) {
this.divisor = newDivisor;
var camValues = cameraCapture.getProperties();
var newWidth = camValues.getStaticProperties().imageWidth / newDivisor.value;
var newHeight = camValues.getStaticProperties().imageHeight / newDivisor.value;
synchronized (streamBufferLock) {
this.streamBuffer = new Mat(newWidth, newHeight, CvType.CV_8UC3);
this.cvSource = CameraServer.getInstance().putVideo(this.name,
cameraCapture.getProperties().getStaticProperties().imageWidth / divisor.value,
cameraCapture.getProperties().getStaticProperties().imageHeight / divisor.value);
}
if (updateUI) {
ServerHandler.sendFullSettings();
}
}
public StreamDivisor getDivisor() {
return divisor;
}
public void setNewVideoMode(VideoMode newVideoMode) {
// Trick to update cvSource and streamBuffer to the new resolution
// Must change the cameraProcess resolution first
setDivisor(divisor, true);
}
public int getStreamPort() {
var s = (MjpegServer) CameraServer.getInstance().getServer("serve_" + name);
return s.getPort();
}
public void runStream(Mat image) {
synchronized (streamBufferLock) {
streamBuffer = image;
}
// if (divisor.value != 1) {
// var camVal = cameraProcess.getProperties().staticProperties;
// var newWidth = camVal.imageWidth / divisor.value;
// var newHeight = camVal.imageHeight / divisor.value;
// Size newSize = new Size(newWidth, newHeight);
// Imgproc.resize(streamBuffer, streamBuffer, newSize);
// }
cvSource.putFrame(streamBuffer);
}
}

View File

@@ -1,52 +0,0 @@
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 double DiagonalAspect;
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();
DiagonalAspect = FastMath.hypot(HorizontalRatio, VerticalRatio);
HorizontalView = FastMath.atan(FastMath.tan(DiagonalView / 2) * (HorizontalRatio / DiagonalAspect)) * 2;
VerticalView = FastMath.atan(FastMath.tan(DiagonalView / 2) * (VerticalRatio / DiagonalAspect)) * 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));
}
}

View File

@@ -0,0 +1,36 @@
package com.chameleonvision.vision.camera;
import org.apache.commons.math3.fraction.Fraction;
import org.apache.commons.math3.util.FastMath;
public class CaptureStaticProperties {
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 horizontalFocalLength;
public final double verticalFocalLength;
public CaptureStaticProperties(int imageWidth, int imageHeight, double fov) {
this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
this.fov = fov;
imageArea = this.imageWidth * this.imageHeight;
centerX = ((double) this.imageWidth / 2) - 0.5;
centerY = ((double) this.imageHeight / 2) - 0.5;
// pinhole model calculations
double diagonalView = FastMath.toRadians(this.fov);
Fraction aspectFraction = new Fraction(this.imageWidth, this.imageHeight);
int horizontalRatio = aspectFraction.getNumerator();
int verticalRatio = aspectFraction.getDenominator();
double diagonalAspect = FastMath.hypot(horizontalRatio, verticalRatio);
double horizontalView = FastMath.atan(FastMath.tan(diagonalView / 2) * (horizontalRatio / diagonalAspect)) * 2;
double verticalView = FastMath.atan(FastMath.tan(diagonalView / 2) * (verticalRatio / diagonalAspect)) * 2;
horizontalFocalLength = this.imageWidth / (2 * FastMath.tan(horizontalView /2));
verticalFocalLength = this.imageHeight / (2 * FastMath.tan(verticalView /2));
}
}

View File

@@ -0,0 +1,78 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.config.CameraJsonConfig;
import edu.wpi.cscore.CvSink;
import edu.wpi.cscore.UsbCamera;
import edu.wpi.cscore.VideoException;
import edu.wpi.cscore.VideoMode;
import edu.wpi.first.cameraserver.CameraServer;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Mat;
public class USBCameraCapture implements CameraCapture {
private final UsbCamera baseCamera;
private final CvSink cvSink;
private Mat imageBuffer = new Mat();
private USBCameraProperties properties;
public USBCameraCapture(CameraJsonConfig config) {
baseCamera = new UsbCamera(config.name, config.path);
cvSink = CameraServer.getInstance().getVideo(baseCamera);
properties = new USBCameraProperties(baseCamera, config);
setVideoMode(properties.videoModes.get(0));
}
@Override
public USBCameraProperties getProperties() {
return properties;
}
@Override
public Pair<Mat, Long> getFrame() {
Long deltaTime;
// TODO: Why multiply by 1000 here?
deltaTime = cvSink.grabFrame(imageBuffer) * 1000L;
return Pair.of(imageBuffer, deltaTime);
}
@Override
public void setExposure(int exposure) {
try {
baseCamera.setExposureManual(exposure);
} catch (VideoException e) {
System.err.println("Failed to change camera exposure!");
}
}
@Override
public void setBrightness(int brightness) {
try {
baseCamera.setBrightness(brightness);
} catch (VideoException e) {
System.err.println("Failed to change camera brightness!");
}
}
@Override
public void setVideoMode(VideoMode mode) {
try {
baseCamera.setVideoMode(mode);
properties.updateVideoMode(mode);
} catch (VideoException e) {
System.err.println("Failed to change camera video mode!");
}
}
@Override
public void setGain(int gain) {
if (properties.isPS3Eye) {
try {
baseCamera.getProperty("gain_automatic").set(0);
baseCamera.getProperty("gain").set(gain);
} catch (Exception e) {
System.err.println("Failed to change camera gain!");
}
}
}
}

View File

@@ -0,0 +1,91 @@
package com.chameleonvision.vision.camera;
import com.chameleonvision.config.CameraJsonConfig;
import com.chameleonvision.util.Platform;
import com.chameleonvision.vision.image.CaptureProperties;
import edu.wpi.cscore.UsbCamera;
import edu.wpi.cscore.VideoMode;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class USBCameraProperties extends CaptureProperties {
public static final double DEFAULT_FOV = 70;
private static final int DEFAULT_EXPOSURE = 50;
private static final int DEFAULT_BRIGHTNESS = 50;
private static final int MINIMUM_FPS = 30;
private static final int MINIMUM_WIDTH = 320;
private static final int MINIMUM_HEIGHT = 200;
private static final int MAX_INIT_MS = 1500;
private static final int PS3EYE_VID = 1415;
private static final int PS3EYE_PID = 2000;
private static final List<VideoMode.PixelFormat> ALLOWED_PIXEL_FORMATS = Arrays.asList(VideoMode.PixelFormat.kYUYV, VideoMode.PixelFormat.kMJPEG);
private static final Predicate<VideoMode> kMinFPSPredicate = (videoMode -> videoMode.fps >= MINIMUM_FPS);
private static final Predicate<VideoMode> kMinSizePredicate = (videoMode -> videoMode.width >= MINIMUM_WIDTH && videoMode.height >= MINIMUM_HEIGHT);
private static final Predicate<VideoMode> kPixelFormatPredicate = (videoMode -> ALLOWED_PIXEL_FORMATS.contains(videoMode.pixelFormat));
public final String name;
public final String path;
public final List<VideoMode> videoModes;
private final UsbCamera baseCamera;
public final boolean isPS3Eye;
private String nickname;
public double FOV;
USBCameraProperties(UsbCamera baseCamera, CameraJsonConfig config) {
FOV = config.fov;
name = config.name;
path = config.path;
nickname = config.nickname;
this.baseCamera = baseCamera;
int usbVID = baseCamera.getInfo().vendorId;
int usbPID = baseCamera.getInfo().productId;
// wait for camera USB init on Windows, Windows USB is slow...
if (Platform.CurrentPlatform == Platform.WINDOWS_64 && !baseCamera.isConnected()) {
System.out.print("Waiting on camera... ");
long initTimeout = System.nanoTime();
while (!baseCamera.isConnected()) {
if (((System.nanoTime() - initTimeout) / 1e6) >= MAX_INIT_MS) {
break;
}
}
var initTimeMs = (System.nanoTime() - initTimeout) / 1e6;
System.out.printf("USBCameraProcess initialized in %.2fms\n", initTimeMs);
}
isPS3Eye = (usbVID == PS3EYE_VID && usbPID == PS3EYE_PID);
videoModes = filterVideoModes(baseCamera.enumerateVideoModes());
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getNickname() {
return nickname;
}
private List<VideoMode> filterVideoModes(VideoMode[] videoModes) {
Predicate<VideoMode> fullPredicate = kMinFPSPredicate.and(kMinSizePredicate).and(kPixelFormatPredicate);
Stream<VideoMode> validModes = Arrays.stream(videoModes).filter(fullPredicate);
return validModes.collect(Collectors.toList());
}
void updateVideoMode(VideoMode videoMode) {
staticProperties = new CaptureStaticProperties(videoMode.width, videoMode.height, FOV);
}
public List<VideoMode> getVideoModes() {
return videoModes;
}
}

View File

@@ -1,4 +1,4 @@
package com.chameleonvision.vision;
package com.chameleonvision.vision.enums;
public enum CalibrationMode {
None,Single,Dual

View File

@@ -0,0 +1,14 @@
package com.chameleonvision.vision.enums;
public enum ImageFlipMode {
NONE(Integer.MIN_VALUE),
VERTICAL(1),
HORIZONTAL(0),
BOTH(-1);
public final int value;
ImageFlipMode(int value) {
this.value = value;
}
}

View File

@@ -0,0 +1,16 @@
package com.chameleonvision.vision.enums;
import org.opencv.core.Core;
public enum ImageRotationMode {
DEG_0(-1),
DEG_90(Core.ROTATE_90_CLOCKWISE),
DEG_180(Core.ROTATE_180),
DEG_270(Core.ROTATE_90_COUNTERCLOCKWISE);
public final int value;
ImageRotationMode(int value) {
this.value = value;
}
}

View File

@@ -1,4 +1,4 @@
package com.chameleonvision.vision;
package com.chameleonvision.vision.enums;
public enum SortMode {
Largest,Smallest,Highest,Lowest,Rightmost,Leftmost,Centermost

View File

@@ -1,10 +1,10 @@
package com.chameleonvision.vision.camera;
package com.chameleonvision.vision.enums;
public enum StreamDivisor {
none(1),
half(2),
quarter(4),
eighth(8);
NONE(1),
HALF(2),
QUARTER(4),
SIXTH(6);
public final Integer value;

View File

@@ -0,0 +1,6 @@
package com.chameleonvision.vision.enums;
public enum TargetGroup {
Single,
Dual
}

View File

@@ -1,4 +1,4 @@
package com.chameleonvision.vision;
package com.chameleonvision.vision.enums;
public enum TargetIntersection {
None,Up,Down,Left,Right

View File

@@ -0,0 +1,20 @@
package com.chameleonvision.vision.image;
import com.chameleonvision.vision.camera.CaptureStaticProperties;
import org.opencv.core.Mat;
public class CaptureProperties {
protected CaptureStaticProperties staticProperties;
protected CaptureProperties() {
}
public CaptureProperties(Mat staticImage, double fov) {
staticProperties = new CaptureStaticProperties(staticImage.cols(), staticImage.rows(), fov);
}
public CaptureStaticProperties getStaticProperties() {
return staticProperties;
}
}

View File

@@ -0,0 +1,12 @@
package com.chameleonvision.vision.image;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Mat;
public interface ImageCapture {
/**
* Get the next camera frame
* @return a Pair of the captured image and the Linux epoch of when the frame was grabbed (in uS)
*/
Pair<Mat, Long> getFrame();
}

View File

@@ -0,0 +1,32 @@
package com.chameleonvision.vision.image;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import java.nio.file.Files;
import java.nio.file.Path;
public class StaticImageCapture implements ImageCapture {
private final Mat image = new Mat();
public StaticImageCapture(Path imagePath) {
if (!Files.exists(imagePath)) throw new RuntimeException("Invalid path for image!");
Mat tempMat = new Mat();
try {
tempMat = Imgcodecs.imread(imagePath.toString());
} catch (Exception e) {
System.err.println("Failed to read image!");
} finally {
tempMat.copyTo(image);
}
}
@Override
public Pair<Mat, Long> getFrame() {
return Pair.of(image, System.nanoTime());
}
}

View File

@@ -0,0 +1,30 @@
package com.chameleonvision.vision.pipeline;
import com.chameleonvision.vision.camera.CameraCapture;
import org.opencv.core.Mat;
/**
*
* @param <R> Pipeline result type
*/
public abstract class CVPipeline<R extends CVPipelineResult, S extends CVPipelineSettings> {
protected Mat outputMat = new Mat();
CameraCapture cameraCapture;
public S settings;
protected CVPipeline(S settings) {
this.settings = settings;
}
protected CVPipeline(String pipelineName, S settings) {
this.settings = settings;
settings.nickname = pipelineName;
}
public void initPipeline(CameraCapture camera) {
cameraCapture = camera;
cameraCapture.setExposure((int) settings.exposure);
cameraCapture.setBrightness((int) settings.brightness);
}
abstract public R runPipeline(Mat inputMat);
}

View File

@@ -0,0 +1,194 @@
package com.chameleonvision.vision.pipeline;
import com.chameleonvision.Main;
import com.chameleonvision.vision.camera.CameraCapture;
import com.chameleonvision.vision.camera.CaptureStaticProperties;
import com.chameleonvision.vision.pipeline.pipes.*;
import com.chameleonvision.vision.enums.ImageRotationMode;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.*;
import java.util.List;
import static com.chameleonvision.vision.pipeline.CVPipeline2d.*;
@SuppressWarnings("WeakerAccess")
public class CVPipeline2d extends CVPipeline<CVPipeline2dResult, CVPipeline2dSettings> {
private Mat rawCameraMat = new Mat();
private RotateFlipPipe rotateFlipPipe;
private BlurPipe blurPipe;
private ErodeDilatePipe erodeDilatePipe;
private HsvPipe hsvPipe;
private FindContoursPipe findContoursPipe;
private FilterContoursPipe filterContoursPipe;
private SpeckleRejectPipe speckleRejectPipe;
private GroupContoursPipe groupContoursPipe;
private SortContoursPipe sortContoursPipe;
private Collect2dTargetsPipe collect2dTargetsPipe;
private Draw2dContoursPipe.Draw2dContoursSettings draw2dContoursSettings;
private Draw2dContoursPipe draw2dContoursPipe;
private OutputMatPipe outputMatPipe;
private StringBuilder pipelineTimeStringBuilder = new StringBuilder();
private CaptureStaticProperties camProps;
private Scalar hsvLower, hsvUpper;
public CVPipeline2d() {
super(new CVPipeline2dSettings());
}
public CVPipeline2d(String name) {
super(name, new CVPipeline2dSettings());
}
public CVPipeline2d(CVPipeline2dSettings settings) {
super(settings);
}
@Override
public void initPipeline(CameraCapture process) {
super.initPipeline(process);
camProps = cameraCapture.getProperties().getStaticProperties();
hsvLower = new Scalar(settings.hue.get(0).intValue(), settings.saturation.get(0).intValue(), settings.value.get(0).intValue());
hsvUpper = new Scalar(settings.hue.get(1).intValue(), settings.saturation.get(1).intValue(), settings.value.get(1).intValue());
rotateFlipPipe = new RotateFlipPipe(ImageRotationMode.DEG_0, settings.flipMode);
blurPipe = new BlurPipe(5);
erodeDilatePipe = new ErodeDilatePipe(settings.erode, settings.dilate, 7);
hsvPipe = new HsvPipe(hsvLower, hsvUpper);
findContoursPipe = new FindContoursPipe();
filterContoursPipe = new FilterContoursPipe(settings.area, settings.ratio, settings.extent, camProps);
speckleRejectPipe = new SpeckleRejectPipe(settings.speckle.doubleValue());
groupContoursPipe = new GroupContoursPipe(settings.targetGroup, settings.targetIntersection);
sortContoursPipe = new SortContoursPipe(settings.sortMode, camProps, 5);
collect2dTargetsPipe = new Collect2dTargetsPipe(settings.calibrationMode, settings.point,
settings.dualTargetCalibrationM, settings.dualTargetCalibrationB, camProps);
draw2dContoursSettings = new Draw2dContoursPipe.Draw2dContoursSettings();
// TODO: make settable from UI? config?
draw2dContoursSettings.showCentroid = false;
draw2dContoursSettings.showCrosshair = true;
draw2dContoursSettings.boxOutlineSize = 2;
draw2dContoursSettings.showRotatedBox = true;
draw2dContoursSettings.showMaximumBox = true;
draw2dContoursPipe = new Draw2dContoursPipe(draw2dContoursSettings, camProps);
outputMatPipe = new OutputMatPipe(settings.isBinary);
}
@Override
public CVPipeline2dResult runPipeline(Mat inputMat) {
long totalPipelineTimeNanos = 0;
long pipelineStartTimeNanos = System.nanoTime();
if (cameraCapture == null) {
throw new RuntimeException("Pipeline was not initialized before being run!");
}
if (inputMat.cols() <= 1) {
throw new RuntimeException("Input Mat is empty!");
}
pipelineTimeStringBuilder = new StringBuilder();
inputMat.copyTo(rawCameraMat);
// prepare pipes
hsvLower = new Scalar(settings.hue.get(0).intValue(), settings.saturation.get(0).intValue(), settings.value.get(0).intValue());
hsvUpper = new Scalar(settings.hue.get(1).intValue(), settings.saturation.get(1).intValue(), settings.value.get(1).intValue());
rotateFlipPipe.setConfig(ImageRotationMode.DEG_0, settings.flipMode);
blurPipe.setConfig(0);
erodeDilatePipe.setConfig(settings.erode, settings.dilate, 7);
hsvPipe.setConfig(hsvLower, hsvUpper);
filterContoursPipe.setConfig(settings.area, settings.ratio, settings.extent, camProps);
speckleRejectPipe.setConfig(settings.speckle.doubleValue());
groupContoursPipe.setConfig(settings.targetGroup, settings.targetIntersection);
sortContoursPipe.setConfig(settings.sortMode, camProps, 5);
collect2dTargetsPipe.setConfig(settings.calibrationMode, settings.point,
settings.dualTargetCalibrationM, settings.dualTargetCalibrationB, camProps);
draw2dContoursPipe.setConfig(camProps);
outputMatPipe.setConfig(settings.isBinary);
long pipeInitTimeNanos = System.nanoTime() - pipelineStartTimeNanos;
// run pipes
Pair<Mat, Long> rotateFlipResult = rotateFlipPipe.run(inputMat);
totalPipelineTimeNanos += rotateFlipResult.getRight();
Pair<Mat, Long> blurResult = blurPipe.run(rotateFlipResult.getLeft());
totalPipelineTimeNanos += blurResult.getRight();
Pair<Mat, Long> erodeDilateResult = erodeDilatePipe.run(blurResult.getLeft());
totalPipelineTimeNanos += erodeDilateResult.getRight();
Pair<Mat, Long> hsvResult = hsvPipe.run(erodeDilateResult.getLeft());
totalPipelineTimeNanos += hsvResult.getRight();
Pair<List<MatOfPoint>, Long> findContoursResult = findContoursPipe.run(hsvResult.getLeft());
totalPipelineTimeNanos += findContoursResult.getRight();
Pair<List<MatOfPoint>, Long> filterContoursResult = filterContoursPipe.run(findContoursResult.getLeft());
totalPipelineTimeNanos += filterContoursResult.getRight();
Pair<List<MatOfPoint>, Long> speckleRejectResult = speckleRejectPipe.run(filterContoursResult.getLeft());
totalPipelineTimeNanos += speckleRejectResult.getRight();
Pair<List<RotatedRect>, Long> groupContoursResult = groupContoursPipe.run(speckleRejectResult.getLeft());
totalPipelineTimeNanos += groupContoursResult.getRight();
Pair<List<RotatedRect>, Long> sortContoursResult = sortContoursPipe.run(groupContoursResult.getLeft());
totalPipelineTimeNanos += sortContoursResult.getRight();
Pair<List<Target2d>, Long> collect2dTargetsResult = collect2dTargetsPipe.run(sortContoursResult.getLeft());
totalPipelineTimeNanos += collect2dTargetsResult.getRight();
// takes pair of (Mat of original camera image (8UC3), Mat of HSV thresholded image(8UC1))
Pair<Mat, Long> outputMatResult = outputMatPipe.run(Pair.of(rawCameraMat, hsvResult.getLeft()));
totalPipelineTimeNanos += outputMatResult.getRight();
// takes pair of (Mat to draw on, List<RotatedRect> of sorted contours)
Pair<Mat, Long> draw2dContoursResult = draw2dContoursPipe.run(Pair.of(outputMatResult.getLeft(), sortContoursResult.getLeft()));
totalPipelineTimeNanos += draw2dContoursResult.getRight();
pipelineTimeStringBuilder.append(String.format("PipeInit: %.2fms, ", pipeInitTimeNanos / 1000000.0));
pipelineTimeStringBuilder.append(String.format("RotateFlip: %.2fms, ", rotateFlipResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("Blur: %.2fms, ", blurResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("ErodeDilate: %.2fms, ", erodeDilateResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("HSV: %.2fms, ", hsvResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("FindContours: %.2fms, ", findContoursResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("FilterContours: %.2fms, ", filterContoursResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("SpeckleReject: %.2fms, ", speckleRejectResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("GroupContours: %.2fms, ", groupContoursResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("SortContours: %.2fms, ", sortContoursResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("Collect2dTargets: %.2fms, ", collect2dTargetsResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("OutputMat: %.2fms, ", outputMatResult.getRight() / 1000000.0));
pipelineTimeStringBuilder.append(String.format("Draw2dContours: %.2fms, ", draw2dContoursResult.getRight() / 1000000.0));
if (Main.testMode) {
System.out.println(pipelineTimeStringBuilder.toString());
double totalPipelineTimeMillis = totalPipelineTimeNanos / 1000000.0;
double totalPipelineTimeFPS = 1.0 / (totalPipelineTimeMillis / 1000.0);
double truePipelineTimeMillis = (System.nanoTime() - pipelineStartTimeNanos) / 1000000.0;
double truePipelineFPS = 1.0 / (truePipelineTimeMillis / 1000.0);
System.out.printf("Pipeline processed in %.3fms (%.2fFPS), ", totalPipelineTimeMillis, totalPipelineTimeFPS);
System.out.printf("full pipeline run time was %.3fms (%.2fFPS)\n", truePipelineTimeMillis, truePipelineFPS);
}
return new CVPipeline2dResult(collect2dTargetsResult.getLeft(), draw2dContoursResult.getLeft(), totalPipelineTimeNanos);
}
public static class CVPipeline2dResult extends CVPipelineResult<Target2d> {
public CVPipeline2dResult(List<Target2d> targets, Mat outputMat, long processTimeNanos) {
super(targets, outputMat, processTimeNanos);
}
}
public static class Target2d {
public double calibratedX = 0.0;
public double calibratedY = 0.0;
public double pitch = 0.0;
public double yaw = 0.0;
public double area = 0.0;
public RotatedRect rawPoint;
}
}

View File

@@ -0,0 +1,29 @@
package com.chameleonvision.vision.pipeline;
import com.chameleonvision.vision.enums.CalibrationMode;
import com.chameleonvision.vision.enums.SortMode;
import com.chameleonvision.vision.enums.TargetGroup;
import com.chameleonvision.vision.enums.TargetIntersection;
import java.util.Arrays;
import java.util.List;
public class CVPipeline2dSettings extends CVPipelineSettings {
public List<Number> hue = Arrays.asList(50, 180);
public List<Number> saturation = Arrays.asList(50, 255);
public List<Number> value = Arrays.asList(50, 255);
public boolean erode = false;
public boolean dilate = false;
public List<Number> area = Arrays.asList(0.0, 100.0);
public List<Number> ratio = Arrays.asList(0.0, 20.0);
public List<Number> extent = Arrays.asList(0, 100);
public Number speckle = 5;
public boolean isBinary = false;
public SortMode sortMode = SortMode.Largest;
public TargetGroup targetGroup = TargetGroup.Single;
public TargetIntersection targetIntersection = TargetIntersection.Up;
public List<Number> point = Arrays.asList(0, 0);
public CalibrationMode calibrationMode = CalibrationMode.None;
public double dualTargetCalibrationM = 1;
public double dualTargetCalibrationB = 0;
}

View File

@@ -0,0 +1,31 @@
package com.chameleonvision.vision.pipeline;
import org.opencv.core.Mat;
import java.util.List;
import static com.chameleonvision.vision.pipeline.CVPipeline3d.*;
public class CVPipeline3d extends CVPipeline<CVPipeline3dResult, CVPipeline3dSettings> {
protected CVPipeline3d(CVPipeline3dSettings settings) {
super(settings);
}
@Override
public CVPipeline3dResult runPipeline(Mat inputMat) {
return null;
}
public static class CVPipeline3dResult extends CVPipelineResult<Target3d> {
public CVPipeline3dResult(List<Target3d> targets, Mat outputMat, long processTime) {
super(targets, outputMat, processTime);
}
}
public static class Target3d {
// TODO: (2.1) Define 3d-specific target data
}
}

View File

@@ -0,0 +1,5 @@
package com.chameleonvision.vision.pipeline;
public class CVPipeline3dSettings extends CVPipeline2dSettings {
// TODO: (2.1) define 3d-specific pipeline settings
}

View File

@@ -0,0 +1,24 @@
package com.chameleonvision.vision.pipeline;
import org.opencv.core.Mat;
import java.util.List;
public abstract class CVPipelineResult<T> {
public final List<T> targets;
public final boolean hasTarget;
public final Mat outputMat = new Mat();
public final long processTime;
public long imageTimestamp = 0;
public CVPipelineResult(List<T> targets, Mat outputMat, long processTime) {
this.targets = targets;
hasTarget = targets != null && !targets.isEmpty();
outputMat.copyTo(this.outputMat);
this.processTime = processTime;
}
public void setTimestamp(long timestamp) {
imageTimestamp = timestamp;
}
}

View File

@@ -0,0 +1,11 @@
package com.chameleonvision.vision.pipeline;
import com.chameleonvision.vision.enums.ImageFlipMode;
@SuppressWarnings("ALL")
public class CVPipelineSettings {
public ImageFlipMode flipMode = ImageFlipMode.NONE;
public String nickname = "New Pipeline";
public double exposure = 50.0;
public double brightness = 50.0;
}

View File

@@ -0,0 +1,38 @@
package com.chameleonvision.vision.pipeline;
import com.chameleonvision.vision.pipeline.pipes.Draw2dContoursPipe;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Mat;
import java.util.List;
import static com.chameleonvision.vision.pipeline.DriverVisionPipeline.DriverPipelineResult;
public class DriverVisionPipeline extends CVPipeline<DriverPipelineResult, CVPipelineSettings> {
public DriverVisionPipeline(CVPipelineSettings settings) {
super(settings);
}
@Override
public DriverPipelineResult runPipeline(Mat inputMat) {
outputMat = inputMat;
var camProps = cameraCapture.getProperties().getStaticProperties();
Draw2dContoursPipe.Draw2dContoursSettings draw2dContoursSettings = new Draw2dContoursPipe.Draw2dContoursSettings();
draw2dContoursSettings.showCrosshair = true;
Draw2dContoursPipe draw2dContoursPipe = new Draw2dContoursPipe(draw2dContoursSettings, camProps);
outputMat = draw2dContoursPipe.run(Pair.of(outputMat, null)).getLeft();
return new DriverPipelineResult(null, inputMat, 0);
}
public static class DriverPipelineResult extends CVPipelineResult<Void> {
public DriverPipelineResult(List<Void> targets, Mat outputMat, long processTime) {
super(targets, outputMat, processTime);
}
}
}

View File

@@ -0,0 +1,44 @@
package com.chameleonvision.vision.pipeline.pipes;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.CvException;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
public class BlurPipe implements Pipe<Mat, Mat> {
private int blurSize;
private Mat processBuffer = new Mat();
private Mat outputMat = new Mat();
public BlurPipe(int blurSize) {
this.blurSize = blurSize;
}
public void setConfig(int blurSize) {
this.blurSize = blurSize;
}
@Override
public Pair<Mat, Long> run(Mat input) {
long processStartNanos = System.nanoTime();
if (blurSize > 0) {
input.copyTo(processBuffer);
try {
Imgproc.blur(processBuffer, processBuffer, new Size(blurSize, blurSize));
processBuffer.copyTo(outputMat);
processBuffer.release();
} catch (CvException e) {
System.err.println("(BlurPipe) Exception thrown by OpenCV: \n" + e.getMessage());
}
} else {
input.copyTo(outputMat);
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(outputMat, processTime);
}
}

View File

@@ -0,0 +1,85 @@
package com.chameleonvision.vision.pipeline.pipes;
import com.chameleonvision.vision.camera.CaptureStaticProperties;
import com.chameleonvision.vision.pipeline.CVPipeline2d;
import com.chameleonvision.vision.enums.CalibrationMode;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.math3.util.FastMath;
import org.opencv.core.RotatedRect;
import java.util.ArrayList;
import java.util.List;
public class Collect2dTargetsPipe implements Pipe<List<RotatedRect>, List<CVPipeline2d.Target2d>> {
private CalibrationMode calibrationMode;
private CaptureStaticProperties camProps;
private List<Number> calibrationPoint;
private double calibrationM, calibrationB;
private List<CVPipeline2d.Target2d> targets = new ArrayList<>();
public Collect2dTargetsPipe(CalibrationMode calibrationMode, List<Number> calibrationPoint,
double calibrationM, double calibrationB, CaptureStaticProperties camProps) {
this.calibrationMode = calibrationMode;
this.camProps = camProps;
this.calibrationPoint = calibrationPoint;
this.calibrationM = calibrationM;
this.calibrationB = calibrationB;
}
public void setConfig(CalibrationMode calibrationMode, List<Number> calibrationPoint,
double calibrationM, double calibrationB, CaptureStaticProperties camProps) {
this.calibrationMode = calibrationMode;
this.camProps = camProps;
this.calibrationPoint = calibrationPoint;
this.calibrationM = calibrationM;
this.calibrationB = calibrationB;
}
@Override
public Pair<List<CVPipeline2d.Target2d>, Long> run(List<RotatedRect> input) {
long processStartNanos = System.nanoTime();
targets.clear();
if (input.size() > 0) {
for (RotatedRect r : input) {
CVPipeline2d.Target2d t = new CVPipeline2d.Target2d();
t.rawPoint = r;
switch (calibrationMode) {
case None:
t.calibratedX = camProps.centerX;
t.calibratedY = camProps.centerY;
break;
case Single:
t.calibratedX = calibrationPoint.get(0).doubleValue();
t.calibratedY = calibrationPoint.get(1).doubleValue();
break;
case Dual:
t.calibratedX = (r.center.y - calibrationB) / calibrationM;
t.calibratedY = (r.center.x * calibrationM) + calibrationB;
break;
}
t.pitch = calculatePitch(r.center.y, t.calibratedY);
t.yaw = calculateYaw(r.center.x, t.calibratedX);
t.area = r.size.area();
targets.add(t);
}
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(targets, processTime);
}
private double calculatePitch(double pixelY, double centerY) {
double pitch = FastMath.toDegrees(FastMath.atan((pixelY - centerY) / camProps.verticalFocalLength));
return (pitch * -1);
}
private double calculateYaw(double pixelX, double centerX) {
return FastMath.toDegrees(FastMath.atan((pixelX - centerX) / camProps.horizontalFocalLength));
}
}

View File

@@ -0,0 +1,94 @@
package com.chameleonvision.vision.pipeline.pipes;
import com.chameleonvision.vision.camera.CaptureStaticProperties;
import com.chameleonvision.util.Helpers;
import com.chameleonvision.vision.image.CaptureProperties;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Point;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class Draw2dContoursPipe implements Pipe<Pair<Mat, List<RotatedRect>>, Mat> {
private final Draw2dContoursSettings settings;
private CaptureStaticProperties camProps;
private Mat processBuffer = new Mat();
private Mat outputMat = new Mat();
public Draw2dContoursPipe(Draw2dContoursSettings settings, CaptureStaticProperties camProps) {
this.settings = settings;
this.camProps = camProps;
}
public void setConfig(CaptureStaticProperties captureProps) {
camProps = captureProps;
}
@Override
public Pair<Mat, Long> run(Pair<Mat, List<RotatedRect>> input) {
long processStartNanos = System.nanoTime();
if (settings.showCrosshair || settings.showCentroid || settings.showMaximumBox || settings.showRotatedBox) {
input.getLeft().copyTo(processBuffer);
if (input.getRight().size() > 0) {
for (RotatedRect r : input.getRight()) {
if (r == null) continue;
List<MatOfPoint> drawnContour = new ArrayList<>();
Point[] vertices = new Point[4];
r.points(vertices);
MatOfPoint contour = new MatOfPoint(vertices);
drawnContour.add(contour);
if (settings.showCentroid) {
Imgproc.circle(processBuffer, r.center, 3, Helpers.colorToScalar(settings.centroidColor));
}
if (settings.showRotatedBox) {
Imgproc.drawContours(processBuffer, drawnContour, 0, Helpers.colorToScalar(settings.rotatedBoxColor), settings.boxOutlineSize);
}
if (settings.showMaximumBox) {
Rect box = Imgproc.boundingRect(contour);
Imgproc.rectangle(processBuffer, new Point(box.x, box.y), new Point((box.x + box.width), (box.y + box.height)), Helpers.colorToScalar(settings.maximumBoxColor), settings.boxOutlineSize);
}
}
}
if (settings.showCrosshair) {
Point xMax = new Point(camProps.centerX + 10, camProps.centerY);
Point xMin = new Point(camProps.centerX - 10, camProps.centerY);
Point yMax = new Point(camProps.centerX, camProps.centerY + 10);
Point yMin = new Point(camProps.centerX, camProps.centerY - 10);
Imgproc.line(processBuffer, xMax, xMin, Helpers.colorToScalar(settings.crosshairColor), 2);
Imgproc.line(processBuffer, yMax, yMin, Helpers.colorToScalar(settings.crosshairColor), 2);
}
processBuffer.copyTo(outputMat);
processBuffer.release();
} else {
input.getLeft().copyTo(outputMat);
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(outputMat, processTime);
}
public static class Draw2dContoursSettings {
public boolean showCentroid = false;
public boolean showCrosshair = false;
public int boxOutlineSize = 0;
public boolean showRotatedBox = false;
public boolean showMaximumBox = false;
public Color centroidColor = Color.GREEN;
public Color crosshairColor = Color.GREEN;
public Color rotatedBoxColor = Color.BLUE;
public Color maximumBoxColor = Color.RED;
}
}

View File

@@ -0,0 +1,53 @@
package com.chameleonvision.vision.pipeline.pipes;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
public class ErodeDilatePipe implements Pipe<Mat, Mat> {
private boolean erode;
private boolean dilate;
private Mat kernel;
private Mat processBuffer = new Mat();
private Mat outputMat = new Mat();
public ErodeDilatePipe(boolean erode, boolean dilate, int kernelSize) {
this.erode = erode;
this.dilate = dilate;
kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(kernelSize, kernelSize));
}
public void setConfig(boolean erode, boolean dilate, int kernelSize) {
this.erode = erode;
this.dilate = dilate;
kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(kernelSize, kernelSize));
}
@Override
public Pair<Mat, Long> run(Mat input) {
long processStartNanos = System.nanoTime();
if (erode || dilate) {
input.copyTo(processBuffer);
if (erode) {
Imgproc.erode(processBuffer, processBuffer, kernel);
}
if (dilate) {
Imgproc.dilate(processBuffer, processBuffer, kernel);
}
processBuffer.copyTo(outputMat);
processBuffer.release();
} else {
input.copyTo(outputMat);
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(outputMat, processTime);
}
}

View File

@@ -0,0 +1,75 @@
package com.chameleonvision.vision.pipeline.pipes;
import com.chameleonvision.vision.camera.CaptureStaticProperties;
import com.chameleonvision.util.MathHandler;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Rect;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.List;
public class FilterContoursPipe implements Pipe<List<MatOfPoint>, List<MatOfPoint>> {
private List<Number> area;
private List<Number> ratio;
private List<Number> extent;
private CaptureStaticProperties camProps;
private List<MatOfPoint> filteredContours = new ArrayList<>();
public FilterContoursPipe(List<Number> area, List<Number> ratio, List<Number> extent, CaptureStaticProperties camProps) {
this.area = area;
this.ratio = ratio;
this.extent = extent;
this.camProps = camProps;
}
public void setConfig(List<Number> area, List<Number> ratio, List<Number> extent, CaptureStaticProperties camProps) {
this.area = area;
this.ratio = ratio;
this.extent = extent;
this.camProps = camProps;
}
@Override
public Pair<List<MatOfPoint>, Long> run(List<MatOfPoint> input) {
long processStartNanos = System.nanoTime();
filteredContours.clear();
if (input.size() > 0) {
for (MatOfPoint Contour : input) {
try {
double contourArea = Imgproc.contourArea(Contour);
double AreaRatio = (contourArea / camProps.imageArea) * 100;
double minArea = (MathHandler.sigmoid(area.get(0)));
double maxArea = (MathHandler.sigmoid(area.get(1)));
if (AreaRatio < minArea || AreaRatio > maxArea) {
continue;
}
var rect = Imgproc.minAreaRect(new MatOfPoint2f(Contour.toArray()));
double minExtent = (extent.get(0).doubleValue() * rect.size.area()) / 100;
double maxExtent = (extent.get(1).doubleValue() * rect.size.area()) / 100;
if (contourArea <= minExtent || contourArea >= maxExtent) {
continue;
}
Rect bb = Imgproc.boundingRect(Contour);
double aspectRatio = ((double)bb.width / bb.height);
if (aspectRatio < ratio.get(0).doubleValue() || aspectRatio > ratio.get(1).doubleValue()) {
continue;
}
filteredContours.add(Contour);
} catch (Exception e) {
System.err.println("Error while filtering contours");
e.printStackTrace();
}
}
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(filteredContours, processTime);
}
}

View File

@@ -0,0 +1,28 @@
package com.chameleonvision.vision.pipeline.pipes;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.List;
public class FindContoursPipe implements Pipe<Mat, List<MatOfPoint>> {
private List<MatOfPoint> foundContours = new ArrayList<>();
public FindContoursPipe() {}
@Override
public Pair<List<MatOfPoint>, Long> run(Mat input) {
long processStartNanos = System.nanoTime();
foundContours.clear();
Imgproc.findContours(input, foundContours, new Mat(), Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_TC89_L1);
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(foundContours, processTime);
}
}

View File

@@ -0,0 +1,166 @@
package com.chameleonvision.vision.pipeline.pipes;
import com.chameleonvision.util.MathHandler;
import com.chameleonvision.vision.enums.TargetGroup;
import com.chameleonvision.vision.enums.TargetIntersection;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import org.opencv.imgproc.Moments;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class GroupContoursPipe implements Pipe<List<MatOfPoint>, List<RotatedRect>> {
private static final Comparator<MatOfPoint> sortByMomentsX =
Comparator.comparingDouble(GroupContoursPipe::calcMomentsX);
private TargetGroup group;
private TargetIntersection intersection;
private List<RotatedRect> groupedContours = new ArrayList<>();
private MatOfPoint2f intersectMatA = new MatOfPoint2f();
private MatOfPoint2f intersectMatB = new MatOfPoint2f();
public GroupContoursPipe(TargetGroup group, TargetIntersection intersection) {
this.group = group;
this.intersection = intersection;
}
public void setConfig(TargetGroup group, TargetIntersection intersection) {
this.group = group;
this.intersection = intersection;
}
@Override
public Pair<List<RotatedRect>, Long> run(List<MatOfPoint> input) {
long processStartNanos = System.nanoTime();
groupedContours.clear();
if (input.size() > 0) {
List<MatOfPoint> sorted = new ArrayList<>(input);
sorted.sort(sortByMomentsX);
Collections.reverse(sorted);
switch (group) {
case Single: {
input.forEach(c -> {
MatOfPoint2f contour = new MatOfPoint2f();
contour.fromArray(c.toArray());
if (contour.cols() != 0 && contour.rows() != 0) {
RotatedRect rect = Imgproc.minAreaRect(contour);
groupedContours.add(rect);
}
});
break;
}
case Dual: {
for (var i = 0; i < input.size(); i++) {
List<Point> finalContourList = new ArrayList<>(input.get(i).toList());
try {
MatOfPoint firstContour = input.get(i);
MatOfPoint secondContour = input.get(i + 1);
if (isIntersecting(firstContour, secondContour)) {
finalContourList.addAll(secondContour.toList());
} else {
finalContourList.clear();
continue;
}
intersectMatA.release();
intersectMatB.release();
firstContour.release();
secondContour.release();
MatOfPoint2f contour = new MatOfPoint2f();
contour.fromList(finalContourList);
if (contour.cols() != 0 && contour.rows() != 0) {
RotatedRect rect = Imgproc.minAreaRect(contour);
groupedContours.add(rect);
}
} catch (IndexOutOfBoundsException e) {
System.err.println("GroupContours: WTF");
finalContourList.clear();
}
}
break;
}
}
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(groupedContours, processTime);
}
private static double calcMomentsX(MatOfPoint c) {
Moments m = Imgproc.moments(c);
return (m.get_m10() / m.get_m00());
}
private boolean isIntersecting(MatOfPoint contourOne, MatOfPoint contourTwo) {
if (intersection.equals(TargetIntersection.None)) {
return true;
}
try {
intersectMatA.fromArray(contourOne.toArray());
intersectMatB.fromArray(contourTwo.toArray());
RotatedRect a = Imgproc.fitEllipse(intersectMatA);
RotatedRect b = Imgproc.fitEllipse(intersectMatB);
double mA = MathHandler.toSlope(a.angle);
double mB = MathHandler.toSlope(b.angle);
double x0A = a.center.x;
double y0A = a.center.y;
double x0B = b.center.x;
double y0B = b.center.y;
double intersectionX = ((mA * x0A) - y0A - (mB * x0B) + y0B) / (mA - mB);
double intersectionY = (mA * (intersectionX - x0A)) + y0A;
double massX = (x0A + x0B) / 2;
double massY = (y0A + y0B) / 2;
switch (intersection) {
case Up: {
if (intersectionY < massY) {
if (mA > 0 && mB < 0) {
return true;
}
}
break;
}
case Down: {
if (intersectionY > massY) {
if (mA < 0 && mB > 0) {
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;
}
}
}

View File

@@ -0,0 +1,48 @@
package com.chameleonvision.vision.pipeline.pipes;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Core;
import org.opencv.core.CvException;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
public class HsvPipe implements Pipe<Mat, Mat> {
private Scalar hsvLower;
private Scalar hsvUpper;
private Mat processBuffer = new Mat();
private Mat outputMat = new Mat();
public HsvPipe(Scalar hsvLower, Scalar hsvUpper) {
this.hsvLower = hsvLower;
this.hsvUpper = hsvUpper;
}
public void setConfig(Scalar hsvLower, Scalar hsvUpper) {
this.hsvLower = hsvLower;
this.hsvUpper = hsvUpper;
}
@Override
public Pair<Mat, Long> run(Mat input) {
long processStartNanos = System.nanoTime();
input.copyTo(processBuffer);
try {
Imgproc.cvtColor(processBuffer, processBuffer, Imgproc.COLOR_BGR2HSV, 3);
Core.inRange(processBuffer, hsvLower, hsvUpper, processBuffer);
} catch (CvException e) {
System.err.println("(HsvPipe) Exception thrown by OpenCV: \n" + e.getMessage());
}
processBuffer.copyTo(outputMat);
processBuffer.release();
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(outputMat, processTime);
}
}

View File

@@ -0,0 +1,49 @@
package com.chameleonvision.vision.pipeline.pipes;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.CvException;
import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
public class OutputMatPipe implements Pipe<Pair<Mat, Mat>, Mat> {
private boolean showThresholded;
private Mat processBuffer = new Mat();
private Mat outputMat = new Mat();
public OutputMatPipe(boolean showThresholded) {
this.showThresholded = showThresholded;
}
public void setConfig(boolean showThresholded) {
this.showThresholded = showThresholded;
}
/**
*
* @param input Input object for pipe
* Left is raw camera mat (8UC3), Right is HSV threshold mat (8UC1)
* @return Returns desired output Mat, and processing time in nanoseconds
*/
@Override
public Pair<Mat, Long> run(Pair<Mat, Mat> input) {
long processStartNanos = System.nanoTime();
if (showThresholded) {
try {
input.getRight().copyTo(processBuffer);
Imgproc.cvtColor(processBuffer, processBuffer, Imgproc.COLOR_GRAY2BGR, 3);
processBuffer.copyTo(outputMat);
processBuffer.release();
} catch (CvException e) {
System.err.println("(OutputMat) Exception thrown by OpenCV: \n" + e.getMessage());
}
} else {
input.getLeft().copyTo(outputMat);
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(outputMat, processTime);
}
}

View File

@@ -0,0 +1,13 @@
package com.chameleonvision.vision.pipeline.pipes;
import org.apache.commons.lang3.tuple.Pair;
public interface Pipe<I, O> {
/**
*
* @param input Input object for pipe
* @return Returns a Pair containing the process time in Nanoseconds,
* and the output object
*/
Pair<O, Long> run(I input);
}

View File

@@ -0,0 +1,54 @@
package com.chameleonvision.vision.pipeline.pipes;
import com.chameleonvision.vision.enums.ImageFlipMode;
import com.chameleonvision.vision.enums.ImageRotationMode;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.Core;
import org.opencv.core.Mat;
public class RotateFlipPipe implements Pipe<Mat, Mat> {
private ImageRotationMode rotation;
private ImageFlipMode flip;
private Mat processBuffer = new Mat();
private Mat outputMat = new Mat();
public RotateFlipPipe(ImageRotationMode rotation, ImageFlipMode flip) {
this.rotation = rotation;
this.flip = flip;
}
public void setConfig(ImageRotationMode rotation, ImageFlipMode flip) {
this.rotation = rotation;
this.flip = flip;
}
@Override
public Pair<Mat, Long> run(Mat input) {
long processStartNanos = System.nanoTime();
boolean shouldFlip = !flip.equals(ImageFlipMode.NONE);
boolean shouldRotate = !rotation.equals(ImageRotationMode.DEG_0);
if (shouldFlip || shouldRotate) {
input.copyTo(processBuffer);
if (shouldFlip) {
Core.flip(processBuffer, processBuffer, flip.value);
}
if (shouldRotate) {
Core.rotate(processBuffer, processBuffer, rotation.value);
}
processBuffer.copyTo(outputMat);
processBuffer.release();
} else {
input.copyTo(outputMat);
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(outputMat, processTime);
}
}

View File

@@ -0,0 +1,87 @@
package com.chameleonvision.vision.pipeline.pipes;
import com.chameleonvision.vision.camera.CaptureStaticProperties;
import com.chameleonvision.vision.enums.SortMode;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.math3.util.FastMath;
import org.opencv.core.RotatedRect;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class SortContoursPipe implements Pipe<List<RotatedRect>, List<RotatedRect>> {
private final Comparator<RotatedRect> SortByCentermostComparator = Comparator.comparingDouble(this::calcCenterDistance);
private static final Comparator<RotatedRect> SortByLargestComparator = (rect1, rect2) -> Double.compare(rect2.size.area(), rect1.size.area());
private static final Comparator<RotatedRect> SortBySmallestComparator = SortByLargestComparator.reversed();
private static final Comparator<RotatedRect> SortByHighestComparator = (rect1, rect2) -> Double.compare(rect2.center.y, rect1.center.y);
private static final Comparator<RotatedRect> SortByLowestComparator = SortByHighestComparator.reversed();
private static final Comparator<RotatedRect> SortByLeftmostComparator = Comparator.comparingDouble(rect -> rect.center.x);
private static final Comparator<RotatedRect> SortByRightmostComparator = SortByLeftmostComparator.reversed();
private SortMode sort;
private CaptureStaticProperties camProps;
private int maxTargets;
private List<RotatedRect> sortedContours = new ArrayList<>();
public SortContoursPipe(SortMode sort, CaptureStaticProperties camProps, int maxTargets) {
this.sort = sort;
this.camProps = camProps;
this.maxTargets = maxTargets;
}
public void setConfig(SortMode sort, CaptureStaticProperties camProps, int maxTargets) {
this.sort = sort;
this.camProps = camProps;
this.maxTargets = maxTargets;
}
@Override
public Pair<List<RotatedRect>, Long> run(List<RotatedRect> input) {
long processStartNanos = System.nanoTime();
sortedContours.clear();
if (input.size() > 0) {
sortedContours.addAll(input.subList(0, Math.min(input.size(), maxTargets - 1)));
switch (sort) {
case Largest:
sortedContours.sort(SortByLargestComparator);
break;
case Smallest:
sortedContours.sort(SortBySmallestComparator);
break;
case Highest:
sortedContours.sort(SortByHighestComparator);
break;
case Lowest:
sortedContours.sort(SortByLowestComparator);
break;
case Leftmost:
sortedContours.sort(SortByLeftmostComparator);
break;
case Rightmost:
sortedContours.sort(SortByRightmostComparator);
break;
case Centermost:
sortedContours.sort(SortByCentermostComparator);
break;
default:
break;
}
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(sortedContours, processTime);
}
private double calcCenterDistance(RotatedRect rect) {
return FastMath.sqrt(FastMath.pow(camProps.centerX - rect.center.x, 2) + FastMath.pow(camProps.centerY - rect.center.y, 2));
}
}

View File

@@ -0,0 +1,51 @@
package com.chameleonvision.vision.pipeline.pipes;
import org.apache.commons.lang3.tuple.Pair;
import org.opencv.core.MatOfPoint;
import org.opencv.imgproc.Imgproc;
import java.util.ArrayList;
import java.util.List;
public class SpeckleRejectPipe implements Pipe<List<MatOfPoint>, List<MatOfPoint>> {
private double minPercentOfAvg;
private List<MatOfPoint> despeckledContours = new ArrayList<>();
public SpeckleRejectPipe(double minPercentOfAvg) {
this.minPercentOfAvg = minPercentOfAvg;
}
public void setConfig(double minPercentOfAvg) {
this.minPercentOfAvg = minPercentOfAvg;
}
@Override
public Pair<List<MatOfPoint>, Long> run(List<MatOfPoint> input) {
long processStartNanos = System.nanoTime();
despeckledContours.clear();
if (input.size() > 0) {
double averageArea = 0.0;
for (MatOfPoint c : input) {
averageArea += Imgproc.contourArea(c);
}
averageArea /= input.size();
double minAllowedArea = minPercentOfAvg / 100.0 * averageArea;
for (MatOfPoint c : input) {
if (Imgproc.contourArea(c) >= minAllowedArea) {
despeckledContours.add(c);
}
}
}
long processTime = System.nanoTime() - processStartNanos;
return Pair.of(despeckledContours, processTime);
}
}

View File

@@ -1,233 +0,0 @@
package com.chameleonvision.vision.process;
import com.chameleonvision.vision.SortMode;
import com.chameleonvision.vision.TargetGroup;
import com.chameleonvision.vision.TargetIntersection;
import com.chameleonvision.vision.camera.CameraValues;
import com.chameleonvision.util.MathHandler;
import org.apache.commons.math3.util.FastMath;
import org.jetbrains.annotations.NotNull;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
import org.opencv.imgproc.Moments;
import java.util.*;
@SuppressWarnings("WeakerAccess")
public class CVProcess {
private final CameraValues cameraValues;
private Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
private Size blur = new Size(3, 3);
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<MatOfPoint> speckleRejectedContours = new ArrayList<>();
private Comparator<MatOfPoint> sortByMomentsX = Comparator.comparingDouble(this::calcMomentsX);
private List<RotatedRect> finalCountours = new ArrayList<>();
private MatOfPoint2f intersectMatA = new MatOfPoint2f();
private MatOfPoint2f intersectMatB = new MatOfPoint2f();
CVProcess(CameraValues cameraValues) {
this.cameraValues = cameraValues;
}
void hsvThreshold(Mat srcImage, Mat dst, @NotNull Scalar hsvLower, @NotNull Scalar hsvUpper, boolean shouldErode, boolean shouldDilate) {
Imgproc.cvtColor(srcImage, hsvImage, Imgproc.COLOR_RGB2HSV, 3);
Imgproc.blur(hsvImage, hsvImage, blur);
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<Number> area, List<Number> ratio, List<Number> extent) {
for (MatOfPoint Contour : inputContours) {
try {
double contourArea = Imgproc.contourArea(Contour);
double AreaRatio = (contourArea / cameraValues.ImageArea) * 100;
double minArea = (MathHandler.sigmoid(area.get(0)));
double maxArea = (MathHandler.sigmoid(area.get(1)));
if (AreaRatio < minArea || AreaRatio > maxArea) {
continue;
}
var rect = Imgproc.minAreaRect(new MatOfPoint2f(Contour.toArray()));
var targetFullness = contourArea;
double minExtent = (double) (extent.get(0).doubleValue() * rect.size.area()) / 100;
double maxExtent = (double) (extent.get(1).doubleValue() * rect.size.area()) / 100;
if (targetFullness <= minExtent || contourArea >= maxExtent) {
continue;
}
Rect bb = Imgproc.boundingRect(Contour);
double aspectRatio = (bb.width / bb.height);
if (aspectRatio < ratio.get(0).doubleValue() || aspectRatio > ratio.get(1).doubleValue()) {
continue;
}
filteredContours.add(Contour);
} catch (Exception e) {
System.err.println("Error while filtering contours");
e.printStackTrace();
}
}
return filteredContours;
}
List<MatOfPoint> rejectSpeckles(List<MatOfPoint> inputContours, Double minimumPercentOfAverage) {
double averageArea = 0.0;
for (MatOfPoint c : inputContours) {
averageArea += Imgproc.contourArea(c);
}
averageArea /= inputContours.size();
var minimumAllowableArea = minimumPercentOfAverage / 100.0 * averageArea;
speckleRejectedContours.clear();
for (MatOfPoint c : inputContours) {
if (Imgproc.contourArea(c) >= minimumAllowableArea) speckleRejectedContours.add(c);
}
return speckleRejectedContours;
}
private double calcDistance(RotatedRect rect) {
return FastMath.sqrt(FastMath.pow(cameraValues.CenterX - rect.center.x, 2) + FastMath.pow(cameraValues.CenterY - rect.center.y, 2));
}
private double calcMomentsX(MatOfPoint c) {
Moments m = Imgproc.moments(c);
return (m.get_m10() / m.get_m00());
}
RotatedRect sortTargetsToOne(List<RotatedRect> inputRects, SortMode 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, TargetIntersection intersectionPoint, TargetGroup targetGroup) {
finalCountours.clear();
inputContours.sort(sortByMomentsX);
Collections.reverse(inputContours);
if (targetGroup.equals(TargetGroup.Dual)) {
for (var i = 0; i < inputContours.size(); i++) {
List<Point> FinalContourList = new ArrayList<>(inputContours.get(i).toList());
try {
MatOfPoint firstContour = inputContours.get(i);
MatOfPoint secondContour = inputContours.get(i + 1);
if (isIntersecting(firstContour, secondContour, intersectionPoint)) {
FinalContourList.addAll(secondContour.toList());
} else {
FinalContourList.clear();
continue;
}
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();
}
}
} else if (targetGroup.equals(TargetGroup.Single)) {
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, TargetIntersection intersectionPoint) {
if (intersectionPoint.equals(TargetIntersection.None)) {
return true;
}
try {
intersectMatA.fromArray(ContourOne.toArray());
intersectMatB.fromArray(ContourTwo.toArray());
RotatedRect a = Imgproc.fitEllipse(intersectMatA);
RotatedRect b = Imgproc.fitEllipse(intersectMatB);
double mA = MathHandler.toSlope(a.angle);
double mB = MathHandler.toSlope(b.angle);
double x0A = a.center.x;
double y0A = a.center.y;
double x0B = b.center.x;
double y0B = b.center.y;
double intersectionX = ((mA * x0A) - y0A - (mB * x0B) + y0B) / (mA - mB);
double intersectionY = (mA * (intersectionX - x0A)) + y0A;
double massX = (x0A + x0B) / 2;
double massY = (y0A + y0B) / 2;
switch (intersectionPoint) {
case Up: {
if (intersectionY < massY) {
if (mA > 0 && mB < 0) {
return true;
}
}
break;
}
case Down: {
if (intersectionY > massY) {
if (mA < 0 && mB > 0) {
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;
}
}
}

View File

@@ -1,76 +0,0 @@
package com.chameleonvision.vision.process;
import com.chameleonvision.vision.camera.Camera;
import com.chameleonvision.vision.camera.StreamDivisor;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
public class CameraProcess implements Runnable {
private final Camera camera;
private final Object outputFrameLock = new Object();
private final Object inputFrameLock = new Object();
private int maxFPS;
private Mat outputFrame;
private Mat inputFrame;
private long timestamp;
private StreamDivisor divisor;
CameraProcess(Camera camera) {
this.camera = camera;
updateFrameSize();
}
public void updateFrameSize() {
maxFPS = camera.getVideoMode().fps;
divisor = camera.getStreamDivisor();
var camVidMode = camera.getVideoMode();
var newWidth = camVidMode.width / divisor.value;
var newHeight = camVidMode.height / divisor.value;
synchronized (outputFrameLock) {
outputFrame = new Mat(newWidth, newHeight, CvType.CV_8UC3);
}
inputFrame = new Mat(camVidMode.width, camVidMode.height, CvType.CV_8UC3);
}
void setOutputFrame(Mat inputFrame) {
synchronized (outputFrameLock) {
inputFrame.copyTo(this.outputFrame);
}
}
long getInputFrame(Mat inputFrame) {
synchronized (inputFrameLock) {
this.inputFrame.copyTo(inputFrame);
return timestamp;
}
}
@Override
public void run() {
while (!Thread.interrupted()) {
synchronized (inputFrameLock) {
timestamp = camera.grabFrame(inputFrame);
}
synchronized (outputFrameLock) {
if (divisor.value != 1) {
var camVidMode = camera.getVideoMode();
var newWidth = camVidMode.width / divisor.value;
var newHeight = camVidMode.height / divisor.value;
Size newSize = new Size(newWidth, newHeight);
Imgproc.resize(outputFrame, outputFrame, newSize);
}
camera.putFrame(outputFrame);
}
var msToWait = (long) 1000 / maxFPS;
try {
Thread.sleep(msToWait);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -1,13 +0,0 @@
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;
public double Area = 0.0;
RotatedRect RawPoint;
}

View File

@@ -1,297 +0,0 @@
package com.chameleonvision.vision.process;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.vision.Orientation;
import com.chameleonvision.vision.Pipeline;
import com.chameleonvision.vision.camera.Camera;
import com.chameleonvision.web.SocketHandler;
import edu.wpi.cscore.VideoException;
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;
public final CameraProcess cameraProcess;
// NetworkTables
public NetworkTableEntry ntPipelineEntry;
public NetworkTableEntry ntDriverModeEntry;
private int ntDriveModeListenerID;
private int ntPipelineListenerID;
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<MatOfPoint> deSpeckledContours = 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 Scalar BoxRectColor = new Scalar(0, 0, 233);
private long timeStamp = 0;
public VisionProcess(Camera processCam) {
camera = processCam;
this.cameraName = camera.name;
initNT(NetworkTableInstance.getDefault().getTable("/chameleon-vision/"+processCam.getNickname()));
// camera settings
cvProcess = new CVProcess(camera.getCamVals());
cameraProcess = new CameraProcess(camera);
}
private void driverModeListener(EntryNotification entryNotification) {
camera.setDriverMode(entryNotification.value.getBoolean());
}
private void pipelineListener(EntryNotification entryNotification) {
var ntPipelineIndex = (int) entryNotification.value.getDouble();
if (ntPipelineIndex >= camera.getPipelines().size()) {
ntPipelineEntry.setNumber(camera.getCurrentPipelineIndex());
} else {
var pipeline = camera.getCurrentPipeline();
camera.setCurrentPipelineIndex(ntPipelineIndex);
try {
camera.setExposure(pipeline.exposure);
} catch (VideoException e) {
System.err.println(e.toString());
}
camera.setBrightness(pipeline.brightness);
if (SettingsManager.GeneralSettings.currentCamera.equals(cameraName)) {
SettingsManager.GeneralSettings.currentPipeline = ntPipelineIndex;
HashMap<String, Object> pipeChange = new HashMap<>();
pipeChange.put("currentPipeline", ntPipelineIndex);
SocketHandler.broadcastMessage(pipeChange);
SocketHandler.sendFullSettings();
}
}
}
private void drawContour(Mat inputMat, RotatedRect contourRect) {
if (contourRect == null) return;
List<MatOfPoint> drawnContour = new ArrayList<>();
Point[] vertices = new Point[4];
contourRect.points(vertices);
MatOfPoint contour = new MatOfPoint(vertices);
drawnContour.add(contour);
Rect box = Imgproc.boundingRect(contour);
Imgproc.drawContours(inputMat, drawnContour, 0, contourRectColor, 3);
Imgproc.circle(inputMat, contourRect.center, 3, contourRectColor);
Imgproc.rectangle(inputMat, new Point(box.x, box.y), new Point((box.x + box.width), (box.y + box.height)), BoxRectColor, 2);
}
private void updateNetworkTables(PipelineResult pipelineResult) {
if (pipelineResult.IsValid) {
ntValidEntry.setBoolean(true);
ntYawEntry.setNumber(pipelineResult.Yaw);
ntPitchEntry.setNumber(pipelineResult.Pitch);
ntDistanceEntry.setNumber(pipelineResult.Area);
ntTimeStampEntry.setNumber(timeStamp);
NetworkTableInstance.getDefault().flush();
} else {
ntYawEntry.setNumber(0.0);
ntPitchEntry.setNumber(0.0);
ntDistanceEntry.setNumber(0.0);
ntTimeStampEntry.setNumber(timeStamp);
ntValidEntry.setBoolean(false);
}
}
private PipelineResult runVisionProcess(Mat inputImage, Mat outputImage) {
var pipelineResult = new PipelineResult();
if (currentPipeline == null) {
return pipelineResult;
}
if (currentPipeline.orientation.equals(Orientation.Inverted)) {
Core.flip(inputImage, inputImage, -1);
}
if (camera.getDriverMode()) {
inputImage.copyTo(outputImage);
return pipelineResult;
}
Scalar hsvLower = new Scalar(currentPipeline.hue.get(0).intValue(), currentPipeline.saturation.get(0).intValue(), currentPipeline.value.get(0).intValue());
Scalar hsvUpper = new Scalar(currentPipeline.hue.get(1).intValue(), currentPipeline.saturation.get(1).intValue(), currentPipeline.value.get(1).intValue());
cvProcess.hsvThreshold(inputImage, hsvThreshMat, hsvLower, hsvUpper, currentPipeline.erode, currentPipeline.dilate);
if (currentPipeline.isBinary) {
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) {
deSpeckledContours = cvProcess.rejectSpeckles(filteredContours, currentPipeline.speckle.doubleValue());
if (deSpeckledContours.size() > 0) {
groupedContours = cvProcess.groupTargets(deSpeckledContours, currentPipeline.targetIntersection, currentPipeline.targetGroup);
if (groupedContours.size() > 0) {
var finalRect = cvProcess.sortTargetsToOne(groupedContours, currentPipeline.sortMode);
pipelineResult.RawPoint = finalRect;
pipelineResult.IsValid = true;
switch (currentPipeline.calibrationMode) {
case None:
///use the center of the camera to find the pitch and yaw difference
pipelineResult.CalibratedX = camera.getCamVals().CenterX;
pipelineResult.CalibratedY = camera.getCamVals().CenterY;
break;
case Single:
// use the static point as a calibration method instead of the center
pipelineResult.CalibratedX = currentPipeline.point.get(0).doubleValue();
pipelineResult.CalibratedY = currentPipeline.point.get(1).doubleValue();
break;
case Dual:
// use the calculated line to find the difference in length between the point and the line
pipelineResult.CalibratedX = (finalRect.center.y - currentPipeline.b) / currentPipeline.m;
pipelineResult.CalibratedY = (finalRect.center.x * currentPipeline.m) + currentPipeline.b;
break;
}
// var camVals = camera.getCamVals();
// if (currentPipeline.isCalibrated) {
// pipelineResult.CalibratedX = (finalRect.center.y - currentPipeline.b) / currentPipeline.m;
// pipelineResult.CalibratedY = (finalRect.center.x * currentPipeline.m) + currentPipeline.b;
// } else {
// pipelineResult.CalibratedX = camVals.CenterX;
// pipelineResult.CalibratedY = camVals.CenterY;
// }
pipelineResult.Pitch = camera.getCamVals().CalculatePitch(finalRect.center.y, pipelineResult.CalibratedY);
pipelineResult.Yaw = camera.getCamVals().CalculateYaw(finalRect.center.x, pipelineResult.CalibratedX);
pipelineResult.Area = finalRect.size.area();
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.getInputFrame(cameraInputMat);
if (cameraInputMat.cols() == 0 && cameraInputMat.rows() == 0) {
continue;
}
// get vision data
var pipelineResult = runVisionProcess(cameraInputMat, streamOutputMat);
updateNetworkTables(pipelineResult);
if (cameraName.equals(SettingsManager.GeneralSettings.currentCamera)) {
HashMap<String, Object> WebSend = new HashMap<>();
HashMap<String, Object> point = new HashMap<>();
HashMap<String, Object> calculated = new HashMap<>();
List<Double> center = new ArrayList<>();
if (pipelineResult.IsValid) {
center.add(pipelineResult.RawPoint.center.x);
center.add(pipelineResult.RawPoint.center.y);
calculated.put("pitch", pipelineResult.Pitch);
calculated.put("yaw", pipelineResult.Yaw);
} else {
center.add(0.0);
center.add(0.0);
calculated.put("pitch", 0);
calculated.put("yaw", 0);
}
point.put("fps", uiFps);
point.put("calculated", calculated);
point.put("rawPoint", center);
WebSend.put("point", point);
SocketHandler.broadcastMessage(WebSend);
}
cameraProcess.setOutputFrame(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());
}
}
}
/**
* Removes the old value change listeners
* calls {@link #initNT}
*
* @param newTable passed to {@link #initNT}
*/
public void resetNT(NetworkTable newTable) {
ntDriverModeEntry.removeListener(ntDriveModeListenerID);
ntPipelineEntry.removeListener(ntPipelineListenerID);
initNT(newTable);
}
/**
* Rebases the writing location for the vision process - pipeline output
*
* @param newTable the new writing location
*/
private void initNT(NetworkTable newTable) {
ntPipelineEntry = newTable.getEntry("pipeline");
ntDriverModeEntry = newTable.getEntry("driver_mode");
ntPitchEntry = newTable.getEntry("pitch");
ntYawEntry = newTable.getEntry("yaw");
ntDistanceEntry = newTable.getEntry("distance");
ntTimeStampEntry = newTable.getEntry("timestamp");
ntValidEntry = newTable.getEntry("is_valid");
ntDriveModeListenerID = ntDriverModeEntry.addListener(this::driverModeListener, EntryListenerFlags.kUpdate);
ntPipelineListenerID = ntPipelineEntry.addListener(this::pipelineListener, EntryListenerFlags.kUpdate);
ntDriverModeEntry.setBoolean(false);
ntPipelineEntry.setNumber(camera.getCurrentPipelineIndex());
}
}

View File

@@ -1,6 +1,6 @@
package com.chameleonvision.web;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.config.ConfigManager;
import io.javalin.Javalin;
public class Server {
@@ -22,7 +22,7 @@ public class Server {
ws.onClose(ctx -> {
socketHandler.onClose(ctx);
System.out.println("Socket Disconnected");
SettingsManager.saveSettings();
ConfigManager.saveGeneralSettings();
});
ws.onBinaryMessage(ctx -> {
socketHandler.onBinaryMessage(ctx);
@@ -32,4 +32,4 @@ public class Server {
app.post("/api/settings/camera", Requesthandler::onCameraSettings);
app.start(port);
}
}
}

View File

@@ -1,23 +1,24 @@
package com.chameleonvision.web;
import com.chameleonvision.settings.GeneralSettings;
import com.chameleonvision.vision.*;
import com.chameleonvision.vision.camera.Camera;
import com.chameleonvision.vision.camera.CameraException;
import com.chameleonvision.settings.SettingsManager;
import com.chameleonvision.vision.camera.CameraManager;
import com.chameleonvision.config.GeneralSettings;
import com.chameleonvision.vision.VisionManager;
import com.chameleonvision.vision.VisionProcess;
import com.chameleonvision.vision.camera.CameraCapture;
import com.chameleonvision.config.ConfigManager;
import com.chameleonvision.vision.enums.CalibrationMode;
import com.chameleonvision.vision.pipeline.CVPipeline;
import com.chameleonvision.vision.pipeline.CVPipeline2dSettings;
import com.chameleonvision.vision.pipeline.CVPipelineSettings;
import com.chameleonvision.vision.enums.StreamDivisor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.wpi.cscore.VideoException;
import io.javalin.websocket.*;
import org.apache.commons.lang3.ArrayUtils;
import org.msgpack.jackson.dataformat.MessagePackFactory;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.nio.ByteBuffer;
import java.util.*;
@@ -37,34 +38,40 @@ public class SocketHandler {
sendFullSettings();
}
public void onClose(WsCloseContext context) {
void onClose(WsCloseContext context) {
users.remove(context);
}
@SuppressWarnings("unchecked")
void onBinaryMessage(WsBinaryMessageContext context) throws Exception {
Map<String, Object> deserialized = objectMapper.readValue(ArrayUtils.toPrimitive(context.data()), new TypeReference<Map<String, Object>>() {
Map<String, Object> deserialized = objectMapper.readValue(ArrayUtils.toPrimitive(context.data()), new TypeReference<>() {
});
for (Map.Entry<String, Object> entry : deserialized.entrySet()) {
try {
VisionProcess currentProcess = VisionManager.getCurrentUIVisionProcess();
CameraCapture currentCamera = currentProcess.getCamera();
CVPipeline currentPipeline = currentProcess.getCurrentPipeline();
switch (entry.getKey()) {
case "driverMode": {
for (HashMap.Entry<String, Object> e : ((HashMap<String, Object>) entry.getValue()).entrySet()) {
setField(CameraManager.getCurrentCamera(), e.getKey(), e.getValue());
}
CameraManager.getCurrentCamera().setDriverMode((Boolean) ((HashMap<String, Object>) entry.getValue()).get("isDriver"));
CameraManager.saveCameras();
HashMap<String, Object> data = (HashMap<String, Object>) entry.getValue();
currentProcess.getDriverModeSettings().exposure = (Integer) data.get("exposure");
currentProcess.getDriverModeSettings().brightness = (Integer) data.get("brightness");
currentProcess.setDriverMode((Boolean) data.get("isDriver"));
VisionManager.saveCurrentCameraDriverMode();
break;
}
case "changeCameraName": {
CameraManager.getCurrentCamera().setNickname((String) entry.getValue());
currentCamera.getProperties().setNickname((String) entry.getValue());
sendFullSettings();
SettingsManager.saveSettings();
VisionManager.saveCurrentCameraSettings();
break;
}
case "changePipelineName": {
CameraManager.getCurrentPipeline().nickname = (String) entry.getValue();
currentPipeline.settings.nickname = ((String) entry.getValue());
sendFullSettings();
SettingsManager.saveSettings();
VisionManager.saveCurrentCameraPipelines();
break;
}
case "duplicatePipeline": {
@@ -72,70 +79,69 @@ public class SocketHandler {
int pipelineIndex = (int) pipelineVals.get("pipeline");
int cameraIndex = (int) pipelineVals.get("camera");
Pipeline origPipeline = CameraManager.getCurrentCamera().getPipelineByIndex(pipelineIndex);
CVPipeline origPipeline = currentProcess.getPipelineByIndex(pipelineIndex);
if (cameraIndex != -1) {
CameraManager.getCameraByIndex(cameraIndex).addPipeline(origPipeline);
VisionProcess newProcess = VisionManager.getVisionProcessByIndex(cameraIndex);
if(newProcess != null) {
newProcess.addPipeline(origPipeline);
}
} else {
CameraManager.getCurrentCamera().addPipeline(origPipeline);
currentProcess.addPipeline(origPipeline);
}
SettingsManager.saveSettings();
VisionManager.saveCurrentCameraPipelines();
break;
}
case "command": {
var cam = CameraManager.getCurrentCamera();
switch ((String) entry.getValue()) {
case "addNewPipeline":
cam.addPipeline();
currentProcess.addPipeline();
sendFullSettings();
SettingsManager.saveSettings();
VisionManager.saveCurrentCameraPipelines();
break;
// TODO: (HIGH) this never worked before, re-visit now that VisionProcess is written sanely
case "deleteCurrentPipeline":
int currentIndex = cam.getCurrentPipelineIndex();
int nextIndex;
if (currentIndex == cam.getPipelines().size() - 1) {
nextIndex = currentIndex - 1;
} else {
nextIndex = currentIndex;
}
cam.deletePipeline();
cam.setCurrentPipelineIndex(nextIndex);
sendFullSettings();
SettingsManager.saveSettings();
// int currentIndex = currentProcess.getCurrentPipelineIndex();
// int nextIndex;
// if (currentIndex == currentProcess.getPipelines().size() - 1) {
// nextIndex = currentIndex - 1;
// } else {
// nextIndex = currentIndex;
// }
// cam.deletePipeline();
// cam.setCurrentPipelineIndex(nextIndex);
// sendFullSettings();
// VisionManager.saveCurrentCameraPipelines();
break;
case "save":
SettingsManager.saveSettings();
System.out.println("saved Settings");
ConfigManager.saveGeneralSettings();
VisionManager.saveAllCameras();
System.out.println("Saved Settings");
break;
}
// used to define all incoming commands
break;
}
case "currentCamera": {
CameraManager.setCurrentCamera((Integer) entry.getValue());
VisionManager.setCurrentProcessByIndex((Integer) entry.getValue());
sendFullSettings();
break;
}
case "currentPipeline": {
var cam = CameraManager.getCurrentCamera();
cam.setCurrentPipelineIndex((Integer) entry.getValue());
currentProcess.setPipeline((Integer) entry.getValue(), true);
sendFullSettings();
try {
cam.setBrightness(cam.getCurrentPipeline().brightness);
cam.setExposure(cam.getCurrentPipeline().exposure);
} catch (Exception e) {
continue;
}
break;
}
default: {
setField(CameraManager.getCurrentCamera().getCurrentPipeline(), entry.getKey(), entry.getValue());
setField(currentPipeline.settings, entry.getKey(), entry.getValue());
switch (entry.getKey()) {
case "exposure": {
CameraManager.getCurrentCamera().setExposure((Integer) entry.getValue());
currentCamera.setExposure((Integer) entry.getValue());
VisionManager.saveCurrentCameraPipelines();
}
case "brightness": {
CameraManager.getCurrentCamera().setBrightness((Integer) entry.getValue());
currentCamera.setBrightness((Integer) entry.getValue());
VisionManager.saveCurrentCameraPipelines();
}
}
break;
@@ -150,36 +156,11 @@ public class SocketHandler {
private void setField(Object obj, String fieldName, Object value) {
try {
if (obj instanceof Camera) {
var cam = (Camera) obj;
switch (fieldName) {
case "driverBrightness":
cam.setDriverBrightness((Integer) value);
break;
case "driverExposure":
cam.setDriverExposure((Integer) value);
break;
case "isDriver":
cam.setDriverMode((boolean) value);
break;
default:
Field field = obj.getClass().getField(fieldName);
if (field.getType().isEnum()) {
field.set(obj, field.getType().getEnumConstants()[(Integer) value]);
} else {
field.set(obj, value);
}
break;
}
} else {
Field field = obj.getClass().getField(fieldName);
if (field.getType().isEnum()) {
field.set(obj, field.getType().getEnumConstants()[(Integer) value]);
} else {
field.set(obj, value);
}
}
Field field = obj.getClass().getField(fieldName);
if (field.getType().isEnum())
field.set(obj, field.getType().getEnumConstants()[(Integer) value]);
else
field.set(obj, value);
} catch (NoSuchFieldException | IllegalAccessException ex) {
ex.printStackTrace();
}
@@ -187,7 +168,7 @@ public class SocketHandler {
private static void broadcastMessage(Object obj, WsContext userToSkip) {
if (users != null)
for (var user : users) {
for (WsContext user : users) {
if (userToSkip != null && user.getSessionId().equals(userToSkip.getSessionId())) {
continue;
}
@@ -204,14 +185,18 @@ public class SocketHandler {
broadcastMessage(obj, null);//Broadcasts the message to every user
}
private static HashMap<String, Object> getOrdinalPipeline() throws CameraException, IllegalAccessException {
private static HashMap<String, Object> getOrdinalPipeline(Class cvClass) throws IllegalAccessException {
HashMap<String, Object> tmp = new HashMap<>();
for (Field f : Pipeline.class.getFields()) {
if (!f.getType().isEnum()) {
tmp.put(f.getName(), f.get(CameraManager.getCurrentCamera().getCurrentPipeline()));
} else {
var i = (Enum) f.get(CameraManager.getCurrentCamera().getCurrentPipeline());
tmp.put(f.getName(), i.ordinal());
for (Field field :cvClass.getFields()) { // iterate over every field in CVPipelineSettings
try {
if (!field.getType().isEnum()) { // if the field is not an enum, get it based on the current pipeline
tmp.put(field.getName(), field.get(VisionManager.getCurrentUIVisionProcess().getCurrentPipeline().settings));
} else {
var ordinal = (Enum) field.get(VisionManager.getCurrentUIVisionProcess().getCurrentPipeline().settings);
tmp.put(field.getName(), ordinal.ordinal());
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
return tmp;
@@ -219,57 +204,58 @@ public class SocketHandler {
private static HashMap<String, Object> getOrdinalSettings() {
HashMap<String, Object> tmp = new HashMap<>();
tmp.put("teamNumber", SettingsManager.GeneralSettings.teamNumber);
tmp.put("connectionType", SettingsManager.GeneralSettings.connectionType.ordinal());
tmp.put("ip", SettingsManager.GeneralSettings.ip);
tmp.put("gateway", SettingsManager.GeneralSettings.gateway);
tmp.put("netmask", SettingsManager.GeneralSettings.netmask);
tmp.put("hostname", SettingsManager.GeneralSettings.hostname);
tmp.put("teamNumber", ConfigManager.settings.teamNumber);
tmp.put("connectionType", ConfigManager.settings.connectionType.ordinal());
tmp.put("ip", ConfigManager.settings.ip);
tmp.put("gateway", ConfigManager.settings.gateway);
tmp.put("netmask", ConfigManager.settings.netmask);
tmp.put("hostname", ConfigManager.settings.hostname);
return tmp;
}
private static HashMap<String, Object> getOrdinalCameraSettings() {
HashMap<String, Object> tmp = new HashMap<>();
try {
var currentCamera = CameraManager.getCurrentCamera();
tmp.put("fov", currentCamera.getFOV());
tmp.put("streamDivisor", currentCamera.getStreamDivisor().ordinal());
tmp.put("resolution", currentCamera.getVideoModeIndex());
} catch (CameraException e) {
e.printStackTrace();
}
VisionProcess currentVisionProcess = VisionManager.getCurrentUIVisionProcess();
CameraCapture currentCamera = VisionManager.getCurrentUIVisionProcess().getCamera();
tmp.put("fov", currentCamera.getProperties().FOV);
tmp.put("streamDivisor", currentVisionProcess.cameraStreamer.getDivisor().ordinal());
// TODO: (HIGH) get videomode index!
// tmp.put("resolution", currentCamera.getVideoModeIndex());
return tmp;
}
private static HashMap<String, Object> getOrdinalDriver() {
HashMap<String, Object> tmp = new HashMap<>();
try {
var currentCamera = CameraManager.getCurrentCamera();
tmp.put("isDriver", currentCamera.getDriverMode());
tmp.put("driverBrightness", currentCamera.getDriverBrightness());
tmp.put("driverExposure", currentCamera.getDriverExposure());
} catch (CameraException e) {
e.printStackTrace();
}
VisionProcess currentProcess = VisionManager.getCurrentUIVisionProcess();
CVPipelineSettings driverModeSettings = currentProcess.getDriverModeSettings();
tmp.put("isDriver", currentProcess.getDriverMode());
tmp.put("driverBrightness", driverModeSettings.brightness);
tmp.put("driverExposure", driverModeSettings.exposure);
return tmp;
}
public static void sendFullSettings() {
//General settings
Map<String, Object> fullSettings = new HashMap<>();
VisionProcess currentProcess = VisionManager.getCurrentUIVisionProcess();
CameraCapture currentCamera = currentProcess.getCamera();
CVPipeline currentPipeline = currentProcess.getCurrentPipeline();
try {
// fullSettings.putAll(settingsToMap(ConfigManager.settings));
// fullSettings.putAll(pipelineToMap(currentPipeline.settings));
fullSettings.put("settings", getOrdinalSettings());
fullSettings.put("cameraSettings", getOrdinalCameraSettings());
fullSettings.put("cameraList", CameraManager.getAllCameraByNickname());
fullSettings.put("pipeline", getOrdinalPipeline());
var currentCamera = CameraManager.getCurrentCamera();
fullSettings.put("cameraList", VisionManager.getAllCameraNicknames());
fullSettings.put("pipeline", getOrdinalPipeline(currentPipeline.settings.getClass()));
fullSettings.put("driverMode", getOrdinalDriver());
fullSettings.put("pipelineList", currentCamera.getPipelinesNickname());
fullSettings.put("resolutionList", currentCamera.getResolutionList());
fullSettings.put("port", currentCamera.getStreamPort());
fullSettings.put("currentPipelineIndex", CameraManager.getCurrentCamera().getCurrentPipelineIndex());
fullSettings.put("currentCameraIndex", CameraManager.getCurrentCameraIndex());
} catch (CameraException | IllegalAccessException e) {
fullSettings.put("pipelineList", VisionManager.getCurrentCameraPipelineNicknames());
fullSettings.put("resolutionList", VisionManager.getCurrentCameraResolutionList());
fullSettings.put("port", currentProcess.cameraStreamer.getStreamPort());
fullSettings.put("currentPipelineIndex", VisionManager.getCurrentUIVisionProcess().getCurrentPipelineIndex());
fullSettings.put("currentCameraIndex", VisionManager.getCurrentUIVisionProcessIndex());
} catch (IllegalAccessException e) {
System.err.println("No camera found!");
}
broadcastMessage(fullSettings);

View File

@@ -0,0 +1,19 @@
package com.chameleonvision.config;
import org.junit.jupiter.api.BeforeAll;
import java.io.IOException;
import java.nio.file.Files;
public class ConfigManagerTest {
@BeforeAll
public void deleteConfig() {
try {
Files.delete(ConfigManager.SettingsPath);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,5 +1,8 @@
# Chameleon-Vision
![CircleCI](https://img.shields.io/circleci/build/github/Chameleon-Vision/chameleon-vision/dev?label=dev&logo=name)
![CircleCI](https://img.shields.io/circleci/build/github/Chameleon-Vision/chameleon-vision/master?label=master&logo=name)
Chameleon Vision is free open-source software for FRC teams to use for vision proccesing on their robots.
## Getting started

View File

@@ -20,7 +20,7 @@ export default new Vuex.Store({
pipeline: {
exposure: 0,
brightness: 0,
orientation: 0,
flipMode: 0,
hue: [0, 15],
saturation: [0, 15],
value: [0, 25],

View File

@@ -2,7 +2,7 @@
<div>
<CVslider name="Exposure" v-model="value.exposure" :min="0" :max="100" @input="handleData('exposure')"/>
<CVslider name="Brightness" v-model="value.brightness" :min="0" :max="100" @input="handleData('brightness')"/>
<CVselect name="Orientation" v-model="value.orientation" :list="['Normal','Inverted']"
<CVselect name="Orientation" v-model="value.flipMode" :list="['Normal','Inverted']"
@input="handleData('orientation')"/>
</div>
</template>

View File

@@ -0,0 +1,4 @@
public class animal{
String name;
}

View File

@@ -0,0 +1,3 @@
public class cat extends animal{
String meow = "mowe";
}

View File

@@ -0,0 +1,3 @@
public class cow extends animal{
String moo = "mooo";
}

View File

@@ -0,0 +1,4 @@
package PACKAGE_NAME;
public class main {
}