diff --git a/.gitignore b/.gitignore index a495e4a21..d7c7d8888 100644 --- a/.gitignore +++ b/.gitignore @@ -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 + diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..cbe5ad167 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/Main/.gitignore b/Main/.gitignore new file mode 100644 index 000000000..31636b185 --- /dev/null +++ b/Main/.gitignore @@ -0,0 +1,5 @@ +bin/* +.settings/* +.project +.classpath +*.prefs \ No newline at end of file diff --git a/Main/chameleon-vision.iml b/Main/chameleon-vision.iml index bb72d8934..a0d6af26a 100644 --- a/Main/chameleon-vision.iml +++ b/Main/chameleon-vision.iml @@ -1,11 +1,14 @@ - + + + + @@ -39,27 +42,34 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Main/pom.xml b/Main/pom.xml index 867a9ab5e..76effa8c3 100644 --- a/Main/pom.xml +++ b/Main/pom.xml @@ -85,12 +85,6 @@ commons-math3 3.6.1 - - - com.google.code.gson - gson - 2.8.5 - org.msgpack msgpack-core @@ -106,10 +100,29 @@ commons-lang3 3.9 + + + com.fasterxml.jackson.core + jackson-core + 2.10.1 + + + + com.fasterxml.jackson.core + jackson-annotations + 2.10.1 + + com.fasterxml.jackson.core jackson-databind - 2.10.0.pr1 + 2.10.1 + + + + org.junit.jupiter + junit-jupiter-engine + 5.5.2 edu.wpi.first.cscore cscore-jni - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf linuxaarch64bionic edu.wpi.first.cscore cscore-jni - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf linuxraspbian edu.wpi.first.cscore cscore-jni - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf linuxx86-64 edu.wpi.first.cscore cscore-jni - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf osxx86-64 edu.wpi.first.cscore cscore-jni - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf windowsx86-64 @@ -161,39 +174,45 @@ edu.wpi.first.cameraserver cameraserver-java - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf edu.wpi.first.ntcore ntcore-java - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf edu.wpi.first.ntcore ntcore-jni - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf osxx86-64 edu.wpi.first.ntcore ntcore-jni - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf linuxraspbian edu.wpi.first.ntcore ntcore-jni - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf linuxx86-64 edu.wpi.first.ntcore ntcore-jni - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf + linuxaarch64bionic + + + edu.wpi.first.ntcore + ntcore-jni + 2020.1.1-beta-2-98-gb058dcf windowsx86-64 @@ -201,43 +220,43 @@ edu.wpi.first.wpiutil wpiutil-java - 2019.4.1-213-g56d782b + 2020.1.1-beta-2-98-gb058dcf edu.wpi.first.thirdparty.frc2020.opencv opencv-java - 3.4.7-1 + 3.4.7-2 edu.wpi.first.thirdparty.frc2020.opencv opencv-jni - 3.4.7-1 + 3.4.7-2 linuxaarch64bionic edu.wpi.first.thirdparty.frc2020.opencv opencv-jni - 3.4.7-1 + 3.4.7-2 linuxraspbian edu.wpi.first.thirdparty.frc2020.opencv opencv-jni - 3.4.7-1 + 3.4.7-2 linuxx86-64 edu.wpi.first.thirdparty.frc2020.opencv opencv-jni - 3.4.7-1 + 3.4.7-2 osxx86-64 edu.wpi.first.thirdparty.frc2020.opencv opencv-jni - 3.4.7-1 + 3.4.7-2 windowsx86-64 diff --git a/Main/src/main/java/com/chameleonvision/Debug.java b/Main/src/main/java/com/chameleonvision/Debug.java new file mode 100644 index 000000000..b90b7d75f --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/Debug.java @@ -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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/Main.java b/Main/src/main/java/com/chameleonvision/Main.java index 59b544707..3e861e0b8 100644 --- a/Main/src/main/java/com/chameleonvision/Main.java +++ b/Main/src/main/java/com/chameleonvision/Main.java @@ -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 { @@ -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); } } diff --git a/Main/src/main/java/com/chameleonvision/config/CameraConfig.java b/Main/src/main/java/com/chameleonvision/config/CameraConfig.java new file mode 100644 index 000000000..8dcf530c3 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/config/CameraConfig.java @@ -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 loadPipelines() { + List 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 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()); + } +} diff --git a/Main/src/main/java/com/chameleonvision/config/CameraJsonConfig.java b/Main/src/main/java/com/chameleonvision/config/CameraJsonConfig.java new file mode 100644 index 000000000..ebd9e6b56 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/config/CameraJsonConfig.java @@ -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()); + } +} diff --git a/Main/src/main/java/com/chameleonvision/config/ConfigManager.java b/Main/src/main/java/com/chameleonvision/config/ConfigManager.java new file mode 100644 index 000000000..60e311cd4 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/config/ConfigManager.java @@ -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 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 initializeCameras(List preliminaryConfigs) { + List 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 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 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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/config/FullCameraConfiguration.java b/Main/src/main/java/com/chameleonvision/config/FullCameraConfiguration.java new file mode 100644 index 000000000..208e007fd --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/config/FullCameraConfiguration.java @@ -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 pipelines; + public final CVPipelineSettings drivermode; + + + public FullCameraConfiguration(CameraJsonConfig cameraConfig, List pipelines, CVPipelineSettings drivermode) { + this.cameraConfig = cameraConfig; + this.pipelines = pipelines; + this.drivermode = drivermode; + } +} diff --git a/Main/src/main/java/com/chameleonvision/settings/GeneralSettings.java b/Main/src/main/java/com/chameleonvision/config/GeneralSettings.java similarity index 90% rename from Main/src/main/java/com/chameleonvision/settings/GeneralSettings.java rename to Main/src/main/java/com/chameleonvision/config/GeneralSettings.java index d2940735d..50f40a7db 100644 --- a/Main/src/main/java/com/chameleonvision/settings/GeneralSettings.java +++ b/Main/src/main/java/com/chameleonvision/config/GeneralSettings.java @@ -1,4 +1,4 @@ -package com.chameleonvision.settings; +package com.chameleonvision.config; import com.chameleonvision.network.NetworkIPMode; diff --git a/Main/src/main/java/com/chameleonvision/network/NetworkInterface.java b/Main/src/main/java/com/chameleonvision/network/NetworkInterface.java index 2c20abf44..7eedcfc88 100644 --- a/Main/src/main/java/com/chameleonvision/network/NetworkInterface.java +++ b/Main/src/main/java/com/chameleonvision/network/NetworkInterface.java @@ -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); diff --git a/Main/src/main/java/com/chameleonvision/network/NetworkManager.java b/Main/src/main/java/com/chameleonvision/network/NetworkManager.java index ad8e1cc15..f9cd51eb4 100644 --- a/Main/src/main/java/com/chameleonvision/network/NetworkManager.java +++ b/Main/src/main/java/com/chameleonvision/network/NetworkManager.java @@ -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) { diff --git a/Main/src/main/java/com/chameleonvision/settings/SettingsManager.java b/Main/src/main/java/com/chameleonvision/settings/SettingsManager.java deleted file mode 100644 index d26444630..000000000 --- a/Main/src/main/java/com/chameleonvision/settings/SettingsManager.java +++ /dev/null @@ -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(); - } - } -} diff --git a/Main/src/main/java/com/chameleonvision/util/FileHelper.java b/Main/src/main/java/com/chameleonvision/util/FileHelper.java deleted file mode 100644 index 8f4628cbe..000000000 --- a/Main/src/main/java/com/chameleonvision/util/FileHelper.java +++ /dev/null @@ -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(); - } - } - } -} diff --git a/Main/src/main/java/com/chameleonvision/util/Helpers.java b/Main/src/main/java/com/chameleonvision/util/Helpers.java new file mode 100644 index 000000000..03d1eca6a --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/util/Helpers.java @@ -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()); + } +} diff --git a/Main/src/main/java/com/chameleonvision/util/JacksonHelper.java b/Main/src/main/java/com/chameleonvision/util/JacksonHelper.java new file mode 100644 index 000000000..68dfd437a --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/util/JacksonHelper.java @@ -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 deserializer(Path path, Class 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; + } +} diff --git a/Main/src/main/java/com/chameleonvision/util/LoopingRunnable.java b/Main/src/main/java/com/chameleonvision/util/LoopingRunnable.java new file mode 100644 index 000000000..555063956 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/util/LoopingRunnable.java @@ -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) {} + } + } +} diff --git a/Main/src/main/java/com/chameleonvision/settings/Platform.java b/Main/src/main/java/com/chameleonvision/util/Platform.java similarity index 95% rename from Main/src/main/java/com/chameleonvision/settings/Platform.java rename to Main/src/main/java/com/chameleonvision/util/Platform.java index 64f70f1f7..4bc4e6712 100644 --- a/Main/src/main/java/com/chameleonvision/settings/Platform.java +++ b/Main/src/main/java/com/chameleonvision/util/Platform.java @@ -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; diff --git a/Main/src/main/java/com/chameleonvision/util/ProgramDirectoryUtilities.java b/Main/src/main/java/com/chameleonvision/util/ProgramDirectoryUtilities.java new file mode 100644 index 000000000..b78b45e91 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/util/ProgramDirectoryUtilities.java @@ -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; + } +} \ No newline at end of file diff --git a/Main/src/main/java/com/chameleonvision/vision/Orientation.java b/Main/src/main/java/com/chameleonvision/vision/Orientation.java deleted file mode 100644 index d5d035f9f..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/Orientation.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.chameleonvision.vision; - -public enum Orientation { - Normal,Inverted//TODO add 90 and 270 deg rotation? -} diff --git a/Main/src/main/java/com/chameleonvision/vision/Pipeline.java b/Main/src/main/java/com/chameleonvision/vision/Pipeline.java deleted file mode 100644 index 4d0bf96db..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/Pipeline.java +++ /dev/null @@ -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 hue = Arrays.asList(50, 180); - public List saturation = Arrays.asList(50, 255); - public List value = Arrays.asList(50, 255); - public boolean erode = false; - public boolean dilate = false; - public List area = Arrays.asList(0.0, 100.0); - public List ratio = Arrays.asList(0.0, 20.0); - public List 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 point = Arrays.asList(0,0); - public CalibrationMode calibrationMode = CalibrationMode.None; - public String nickname; -} diff --git a/Main/src/main/java/com/chameleonvision/vision/TargetGroup.java b/Main/src/main/java/com/chameleonvision/vision/TargetGroup.java deleted file mode 100644 index 278e53852..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/TargetGroup.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.chameleonvision.vision; - -public enum TargetGroup { - Single,Dual -} diff --git a/Main/src/main/java/com/chameleonvision/vision/VisionManager.java b/Main/src/main/java/com/chameleonvision/vision/VisionManager.java new file mode 100644 index 000000000..59c190348 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/VisionManager.java @@ -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 usbCameraInfosByCameraName = new LinkedHashMap<>(); + private static final LinkedList loadedCameraConfigs = new LinkedList<>(); + private static final LinkedList 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 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 getAllCameraNicknames() { + return visionProcesses.stream().map(vpm -> vpm.visionProcess.getCamera() + .getProperties().getNickname()).collect(Collectors.toList()); + } + + public static List 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 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 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 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; + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/VisionProcess.java b/Main/src/main/java/com/chameleonvision/vision/VisionProcess.java new file mode 100644 index 000000000..89e7b51c2 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/VisionProcess.java @@ -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 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 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 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 WebSend = new HashMap<>(); + HashMap point = new HashMap<>(); + HashMap calculated = new HashMap<>(); + List 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 targets = (List) 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 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 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(); + } + } + } + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CamVideoMode.java b/Main/src/main/java/com/chameleonvision/vision/camera/CamVideoMode.java deleted file mode 100644 index 3c6bdaad0..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/camera/CamVideoMode.java +++ /dev/null @@ -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; - } - } -} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/Camera.java b/Main/src/main/java/com/chameleonvision/vision/camera/Camera.java deleted file mode 100644 index 349e46b33..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/camera/Camera.java +++ /dev/null @@ -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 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 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 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 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 getPipelines() { - return pipelines; - } - - public List 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 getResolutionList() { - return Arrays.stream(availableVideoModes) - .map(res -> new HashMap(){{ - 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; - } -} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CameraCapture.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraCapture.java new file mode 100644 index 000000000..152bccaa0 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/camera/CameraCapture.java @@ -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); +} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CameraDeserializer.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraDeserializer.java deleted file mode 100644 index 88152ca3e..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/camera/CameraDeserializer.java +++ /dev/null @@ -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 { - @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 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; - } - } -} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CameraException.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraException.java deleted file mode 100644 index f4c5671e3..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/camera/CameraException.java +++ /dev/null @@ -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()); - } -} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CameraManager.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraManager.java deleted file mode 100644 index 1fcdd9f99..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/camera/CameraManager.java +++ /dev/null @@ -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 AllCamerasByName = new LinkedHashMap<>(); - public static HashMap AllVisionProcessesByName = new HashMap<>(); - - static HashMap 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 getAllCamerasByName() { - return AllCamerasByName; - } - public static List 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 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 s = new ArrayList(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(); - } - } - } -} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CameraSerializer.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraSerializer.java deleted file mode 100644 index 3022af208..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/camera/CameraSerializer.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.chameleonvision.vision.camera; -import com.google.gson.*; - -import java.lang.reflect.Type; - -public class CameraSerializer implements JsonSerializer { - @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; - } -} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CameraStreamer.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraStreamer.java new file mode 100644 index 000000000..1a2e68aa6 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/camera/CameraStreamer.java @@ -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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CameraValues.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraValues.java deleted file mode 100644 index f429b98d2..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/camera/CameraValues.java +++ /dev/null @@ -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)); - } -} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CaptureStaticProperties.java b/Main/src/main/java/com/chameleonvision/vision/camera/CaptureStaticProperties.java new file mode 100644 index 000000000..4c991f5d6 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/camera/CaptureStaticProperties.java @@ -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)); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/USBCameraCapture.java b/Main/src/main/java/com/chameleonvision/vision/camera/USBCameraCapture.java new file mode 100644 index 000000000..d7912b428 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/camera/USBCameraCapture.java @@ -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 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!"); + } + } + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/USBCameraProperties.java b/Main/src/main/java/com/chameleonvision/vision/camera/USBCameraProperties.java new file mode 100644 index 000000000..d98e221ba --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/camera/USBCameraProperties.java @@ -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 ALLOWED_PIXEL_FORMATS = Arrays.asList(VideoMode.PixelFormat.kYUYV, VideoMode.PixelFormat.kMJPEG); + + private static final Predicate kMinFPSPredicate = (videoMode -> videoMode.fps >= MINIMUM_FPS); + private static final Predicate kMinSizePredicate = (videoMode -> videoMode.width >= MINIMUM_WIDTH && videoMode.height >= MINIMUM_HEIGHT); + private static final Predicate kPixelFormatPredicate = (videoMode -> ALLOWED_PIXEL_FORMATS.contains(videoMode.pixelFormat)); + + public final String name; + public final String path; + public final List 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 filterVideoModes(VideoMode[] videoModes) { + Predicate fullPredicate = kMinFPSPredicate.and(kMinSizePredicate).and(kPixelFormatPredicate); + Stream 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 getVideoModes() { + return videoModes; + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/CalibrationMode.java b/Main/src/main/java/com/chameleonvision/vision/enums/CalibrationMode.java similarity index 56% rename from Main/src/main/java/com/chameleonvision/vision/CalibrationMode.java rename to Main/src/main/java/com/chameleonvision/vision/enums/CalibrationMode.java index 64de036ed..42e14407e 100644 --- a/Main/src/main/java/com/chameleonvision/vision/CalibrationMode.java +++ b/Main/src/main/java/com/chameleonvision/vision/enums/CalibrationMode.java @@ -1,4 +1,4 @@ -package com.chameleonvision.vision; +package com.chameleonvision.vision.enums; public enum CalibrationMode { None,Single,Dual diff --git a/Main/src/main/java/com/chameleonvision/vision/enums/ImageFlipMode.java b/Main/src/main/java/com/chameleonvision/vision/enums/ImageFlipMode.java new file mode 100644 index 000000000..3bfb9a073 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/enums/ImageFlipMode.java @@ -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; + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/enums/ImageRotationMode.java b/Main/src/main/java/com/chameleonvision/vision/enums/ImageRotationMode.java new file mode 100644 index 000000000..c7920b46d --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/enums/ImageRotationMode.java @@ -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; + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/SortMode.java b/Main/src/main/java/com/chameleonvision/vision/enums/SortMode.java similarity index 68% rename from Main/src/main/java/com/chameleonvision/vision/SortMode.java rename to Main/src/main/java/com/chameleonvision/vision/enums/SortMode.java index e97322230..5ac43650f 100644 --- a/Main/src/main/java/com/chameleonvision/vision/SortMode.java +++ b/Main/src/main/java/com/chameleonvision/vision/enums/SortMode.java @@ -1,4 +1,4 @@ -package com.chameleonvision.vision; +package com.chameleonvision.vision.enums; public enum SortMode { Largest,Smallest,Highest,Lowest,Rightmost,Leftmost,Centermost diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/StreamDivisor.java b/Main/src/main/java/com/chameleonvision/vision/enums/StreamDivisor.java similarity index 56% rename from Main/src/main/java/com/chameleonvision/vision/camera/StreamDivisor.java rename to Main/src/main/java/com/chameleonvision/vision/enums/StreamDivisor.java index bdda9556c..45c487cde 100644 --- a/Main/src/main/java/com/chameleonvision/vision/camera/StreamDivisor.java +++ b/Main/src/main/java/com/chameleonvision/vision/enums/StreamDivisor.java @@ -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; diff --git a/Main/src/main/java/com/chameleonvision/vision/enums/TargetGroup.java b/Main/src/main/java/com/chameleonvision/vision/enums/TargetGroup.java new file mode 100644 index 000000000..f1fef709c --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/enums/TargetGroup.java @@ -0,0 +1,6 @@ +package com.chameleonvision.vision.enums; + +public enum TargetGroup { + Single, + Dual +} diff --git a/Main/src/main/java/com/chameleonvision/vision/TargetIntersection.java b/Main/src/main/java/com/chameleonvision/vision/enums/TargetIntersection.java similarity index 60% rename from Main/src/main/java/com/chameleonvision/vision/TargetIntersection.java rename to Main/src/main/java/com/chameleonvision/vision/enums/TargetIntersection.java index 34e75b2bf..780f2b01a 100644 --- a/Main/src/main/java/com/chameleonvision/vision/TargetIntersection.java +++ b/Main/src/main/java/com/chameleonvision/vision/enums/TargetIntersection.java @@ -1,4 +1,4 @@ -package com.chameleonvision.vision; +package com.chameleonvision.vision.enums; public enum TargetIntersection { None,Up,Down,Left,Right diff --git a/Main/src/main/java/com/chameleonvision/vision/image/CaptureProperties.java b/Main/src/main/java/com/chameleonvision/vision/image/CaptureProperties.java new file mode 100644 index 000000000..139023fcd --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/image/CaptureProperties.java @@ -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; + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/image/ImageCapture.java b/Main/src/main/java/com/chameleonvision/vision/image/ImageCapture.java new file mode 100644 index 000000000..1d7b3820e --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/image/ImageCapture.java @@ -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 getFrame(); +} diff --git a/Main/src/main/java/com/chameleonvision/vision/image/StaticImageCapture.java b/Main/src/main/java/com/chameleonvision/vision/image/StaticImageCapture.java new file mode 100644 index 000000000..3f738f118 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/image/StaticImageCapture.java @@ -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 getFrame() { + return Pair.of(image, System.nanoTime()); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline.java new file mode 100644 index 000000000..52970d204 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline.java @@ -0,0 +1,30 @@ +package com.chameleonvision.vision.pipeline; + +import com.chameleonvision.vision.camera.CameraCapture; +import org.opencv.core.Mat; + +/** + * + * @param Pipeline result type + */ +public abstract class CVPipeline { + 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); +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline2d.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline2d.java new file mode 100644 index 000000000..b377d5fff --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline2d.java @@ -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 { + + 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 rotateFlipResult = rotateFlipPipe.run(inputMat); + totalPipelineTimeNanos += rotateFlipResult.getRight(); + + Pair blurResult = blurPipe.run(rotateFlipResult.getLeft()); + totalPipelineTimeNanos += blurResult.getRight(); + + Pair erodeDilateResult = erodeDilatePipe.run(blurResult.getLeft()); + totalPipelineTimeNanos += erodeDilateResult.getRight(); + + Pair hsvResult = hsvPipe.run(erodeDilateResult.getLeft()); + totalPipelineTimeNanos += hsvResult.getRight(); + + Pair, Long> findContoursResult = findContoursPipe.run(hsvResult.getLeft()); + totalPipelineTimeNanos += findContoursResult.getRight(); + + Pair, Long> filterContoursResult = filterContoursPipe.run(findContoursResult.getLeft()); + totalPipelineTimeNanos += filterContoursResult.getRight(); + + Pair, Long> speckleRejectResult = speckleRejectPipe.run(filterContoursResult.getLeft()); + totalPipelineTimeNanos += speckleRejectResult.getRight(); + + Pair, Long> groupContoursResult = groupContoursPipe.run(speckleRejectResult.getLeft()); + totalPipelineTimeNanos += groupContoursResult.getRight(); + + Pair, Long> sortContoursResult = sortContoursPipe.run(groupContoursResult.getLeft()); + totalPipelineTimeNanos += sortContoursResult.getRight(); + + Pair, 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 outputMatResult = outputMatPipe.run(Pair.of(rawCameraMat, hsvResult.getLeft())); + totalPipelineTimeNanos += outputMatResult.getRight(); + + // takes pair of (Mat to draw on, List of sorted contours) + Pair 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 { + public CVPipeline2dResult(List 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; + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline2dSettings.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline2dSettings.java new file mode 100644 index 000000000..1f29f55ef --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline2dSettings.java @@ -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 hue = Arrays.asList(50, 180); + public List saturation = Arrays.asList(50, 255); + public List value = Arrays.asList(50, 255); + public boolean erode = false; + public boolean dilate = false; + public List area = Arrays.asList(0.0, 100.0); + public List ratio = Arrays.asList(0.0, 20.0); + public List 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 point = Arrays.asList(0, 0); + public CalibrationMode calibrationMode = CalibrationMode.None; + public double dualTargetCalibrationM = 1; + public double dualTargetCalibrationB = 0; +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline3d.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline3d.java new file mode 100644 index 000000000..ddf75c38a --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline3d.java @@ -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 { + + + protected CVPipeline3d(CVPipeline3dSettings settings) { + super(settings); + } + + @Override + public CVPipeline3dResult runPipeline(Mat inputMat) { + return null; + } + + + public static class CVPipeline3dResult extends CVPipelineResult { + public CVPipeline3dResult(List targets, Mat outputMat, long processTime) { + super(targets, outputMat, processTime); + } + } + + public static class Target3d { + // TODO: (2.1) Define 3d-specific target data + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline3dSettings.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline3dSettings.java new file mode 100644 index 000000000..8085582dd --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipeline3dSettings.java @@ -0,0 +1,5 @@ +package com.chameleonvision.vision.pipeline; + +public class CVPipeline3dSettings extends CVPipeline2dSettings { + // TODO: (2.1) define 3d-specific pipeline settings +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipelineResult.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipelineResult.java new file mode 100644 index 000000000..81914f0cc --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipelineResult.java @@ -0,0 +1,24 @@ +package com.chameleonvision.vision.pipeline; + +import org.opencv.core.Mat; + +import java.util.List; + +public abstract class CVPipelineResult { + public final List targets; + public final boolean hasTarget; + public final Mat outputMat = new Mat(); + public final long processTime; + public long imageTimestamp = 0; + + public CVPipelineResult(List 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; + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipelineSettings.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipelineSettings.java new file mode 100644 index 000000000..4dc799caf --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/CVPipelineSettings.java @@ -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; +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/DriverVisionPipeline.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/DriverVisionPipeline.java new file mode 100644 index 000000000..c3324a555 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/DriverVisionPipeline.java @@ -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 { + + 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 { + public DriverPipelineResult(List targets, Mat outputMat, long processTime) { + super(targets, outputMat, processTime); + } + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/BlurPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/BlurPipe.java new file mode 100644 index 000000000..eeb175356 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/BlurPipe.java @@ -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 { + + 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 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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/Collect2dTargetsPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/Collect2dTargetsPipe.java new file mode 100644 index 000000000..d1c0fb960 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/Collect2dTargetsPipe.java @@ -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> { + + private CalibrationMode calibrationMode; + private CaptureStaticProperties camProps; + private List calibrationPoint; + private double calibrationM, calibrationB; + + private List targets = new ArrayList<>(); + + public Collect2dTargetsPipe(CalibrationMode calibrationMode, List 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 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, Long> run(List 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)); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/Draw2dContoursPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/Draw2dContoursPipe.java new file mode 100644 index 000000000..69481fb68 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/Draw2dContoursPipe.java @@ -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>, 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 run(Pair> 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 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; + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/ErodeDilatePipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/ErodeDilatePipe.java new file mode 100644 index 000000000..215e3ecdd --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/ErodeDilatePipe.java @@ -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 { + + 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 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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/FilterContoursPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/FilterContoursPipe.java new file mode 100644 index 000000000..121216214 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/FilterContoursPipe.java @@ -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> { + + private List area; + private List ratio; + private List extent; + private CaptureStaticProperties camProps; + + private List filteredContours = new ArrayList<>(); + + public FilterContoursPipe(List area, List ratio, List extent, CaptureStaticProperties camProps) { + this.area = area; + this.ratio = ratio; + this.extent = extent; + this.camProps = camProps; + } + + public void setConfig(List area, List ratio, List extent, CaptureStaticProperties camProps) { + this.area = area; + this.ratio = ratio; + this.extent = extent; + this.camProps = camProps; + } + + @Override + public Pair, Long> run(List 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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/FindContoursPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/FindContoursPipe.java new file mode 100644 index 000000000..bf57d57d1 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/FindContoursPipe.java @@ -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> { + + private List foundContours = new ArrayList<>(); + + public FindContoursPipe() {} + + @Override + public Pair, 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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/GroupContoursPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/GroupContoursPipe.java new file mode 100644 index 000000000..b2d3df9aa --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/GroupContoursPipe.java @@ -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> { + + private static final Comparator sortByMomentsX = + Comparator.comparingDouble(GroupContoursPipe::calcMomentsX); + + private TargetGroup group; + private TargetIntersection intersection; + + private List 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, Long> run(List input) { + long processStartNanos = System.nanoTime(); + + groupedContours.clear(); + + if (input.size() > 0) { + + List 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 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; + } + } +} \ No newline at end of file diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/HsvPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/HsvPipe.java new file mode 100644 index 000000000..9a271e8bd --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/HsvPipe.java @@ -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 { + + 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 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); + } +} + diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/OutputMatPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/OutputMatPipe.java new file mode 100644 index 000000000..82d02228b --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/OutputMatPipe.java @@ -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, 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 run(Pair 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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/Pipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/Pipe.java new file mode 100644 index 000000000..d2458a6c7 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/Pipe.java @@ -0,0 +1,13 @@ +package com.chameleonvision.vision.pipeline.pipes; + +import org.apache.commons.lang3.tuple.Pair; + +public interface Pipe { + /** + * + * @param input Input object for pipe + * @return Returns a Pair containing the process time in Nanoseconds, + * and the output object + */ + Pair run(I input); +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/RotateFlipPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/RotateFlipPipe.java new file mode 100644 index 000000000..4ae451b84 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/RotateFlipPipe.java @@ -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 { + + 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 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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/SortContoursPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/SortContoursPipe.java new file mode 100644 index 000000000..ee10cf099 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/SortContoursPipe.java @@ -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> { + + private final Comparator SortByCentermostComparator = Comparator.comparingDouble(this::calcCenterDistance); + + private static final Comparator SortByLargestComparator = (rect1, rect2) -> Double.compare(rect2.size.area(), rect1.size.area()); + private static final Comparator SortBySmallestComparator = SortByLargestComparator.reversed(); + + private static final Comparator SortByHighestComparator = (rect1, rect2) -> Double.compare(rect2.center.y, rect1.center.y); + private static final Comparator SortByLowestComparator = SortByHighestComparator.reversed(); + + private static final Comparator SortByLeftmostComparator = Comparator.comparingDouble(rect -> rect.center.x); + private static final Comparator SortByRightmostComparator = SortByLeftmostComparator.reversed(); + + private SortMode sort; + private CaptureStaticProperties camProps; + private int maxTargets; + + private List 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, Long> run(List 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)); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/SpeckleRejectPipe.java b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/SpeckleRejectPipe.java new file mode 100644 index 000000000..058e955d6 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/pipeline/pipes/SpeckleRejectPipe.java @@ -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> { + + private double minPercentOfAvg; + + private List despeckledContours = new ArrayList<>(); + + public SpeckleRejectPipe(double minPercentOfAvg) { + this.minPercentOfAvg = minPercentOfAvg; + } + + public void setConfig(double minPercentOfAvg) { + this.minPercentOfAvg = minPercentOfAvg; + } + + @Override + public Pair, Long> run(List 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); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/process/CVProcess.java b/Main/src/main/java/com/chameleonvision/vision/process/CVProcess.java deleted file mode 100644 index 51256291a..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/process/CVProcess.java +++ /dev/null @@ -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 foundContours = new ArrayList<>(); - private Mat binaryMat = new Mat(); - private List filteredContours = new ArrayList<>(); - private Comparator sortByCentermostComparator = Comparator.comparingDouble(this::calcDistance); - private List speckleRejectedContours = new ArrayList<>(); - private Comparator sortByMomentsX = Comparator.comparingDouble(this::calcMomentsX); - private List 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 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 filterContours(List inputContours, List area, List ratio, List 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 rejectSpeckles(List 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 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 groupTargets(List 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 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; - } - } -} diff --git a/Main/src/main/java/com/chameleonvision/vision/process/CameraProcess.java b/Main/src/main/java/com/chameleonvision/vision/process/CameraProcess.java deleted file mode 100644 index a9dd5958c..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/process/CameraProcess.java +++ /dev/null @@ -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(); - } - } - } -} diff --git a/Main/src/main/java/com/chameleonvision/vision/process/PipelineResult.java b/Main/src/main/java/com/chameleonvision/vision/process/PipelineResult.java deleted file mode 100644 index bbb2dda82..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/process/PipelineResult.java +++ /dev/null @@ -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; -} diff --git a/Main/src/main/java/com/chameleonvision/vision/process/VisionProcess.java b/Main/src/main/java/com/chameleonvision/vision/process/VisionProcess.java deleted file mode 100644 index f412ea6db..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/process/VisionProcess.java +++ /dev/null @@ -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 foundContours = new ArrayList<>(); - private List filteredContours = new ArrayList<>(); - private List deSpeckledContours = new ArrayList<>(); - private List 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 pipeChange = new HashMap<>(); - pipeChange.put("currentPipeline", ntPipelineIndex); - SocketHandler.broadcastMessage(pipeChange); - SocketHandler.sendFullSettings(); - - } - } - } - - private void drawContour(Mat inputMat, RotatedRect contourRect) { - if (contourRect == null) return; - List 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 WebSend = new HashMap<>(); - HashMap point = new HashMap<>(); - HashMap calculated = new HashMap<>(); - List 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()); - } -} diff --git a/Main/src/main/java/com/chameleonvision/web/Server.java b/Main/src/main/java/com/chameleonvision/web/Server.java index 020c039cd..d17aadc9c 100644 --- a/Main/src/main/java/com/chameleonvision/web/Server.java +++ b/Main/src/main/java/com/chameleonvision/web/Server.java @@ -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); } -} \ No newline at end of file +} diff --git a/Main/src/main/java/com/chameleonvision/web/SocketHandler.java b/Main/src/main/java/com/chameleonvision/web/SocketHandler.java index 46e50b3aa..1c75d88de 100644 --- a/Main/src/main/java/com/chameleonvision/web/SocketHandler.java +++ b/Main/src/main/java/com/chameleonvision/web/SocketHandler.java @@ -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 deserialized = objectMapper.readValue(ArrayUtils.toPrimitive(context.data()), new TypeReference>() { + Map deserialized = objectMapper.readValue(ArrayUtils.toPrimitive(context.data()), new TypeReference<>() { }); for (Map.Entry entry : deserialized.entrySet()) { try { + VisionProcess currentProcess = VisionManager.getCurrentUIVisionProcess(); + CameraCapture currentCamera = currentProcess.getCamera(); + CVPipeline currentPipeline = currentProcess.getCurrentPipeline(); + switch (entry.getKey()) { case "driverMode": { - for (HashMap.Entry e : ((HashMap) entry.getValue()).entrySet()) { - setField(CameraManager.getCurrentCamera(), e.getKey(), e.getValue()); - } - CameraManager.getCurrentCamera().setDriverMode((Boolean) ((HashMap) entry.getValue()).get("isDriver")); - CameraManager.saveCameras(); + HashMap data = (HashMap) 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 getOrdinalPipeline() throws CameraException, IllegalAccessException { + private static HashMap getOrdinalPipeline(Class cvClass) throws IllegalAccessException { HashMap 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 getOrdinalSettings() { HashMap 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 getOrdinalCameraSettings() { HashMap 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 getOrdinalDriver() { HashMap 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 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); diff --git a/Main/src/test/java/com/chameleonvision/config/ConfigManagerTest.java b/Main/src/test/java/com/chameleonvision/config/ConfigManagerTest.java new file mode 100644 index 000000000..e3f495235 --- /dev/null +++ b/Main/src/test/java/com/chameleonvision/config/ConfigManagerTest.java @@ -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(); + } + } + +} diff --git a/README.md b/README.md index 9c3e57522..cb31742b5 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/chameleon-client/src/store.js b/chameleon-client/src/store.js index 1a7a45987..6b44eb550 100644 --- a/chameleon-client/src/store.js +++ b/chameleon-client/src/store.js @@ -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], diff --git a/chameleon-client/src/views/CameraViewes/InputTab.vue b/chameleon-client/src/views/CameraViewes/InputTab.vue index 4f1681739..c872e0109 100644 --- a/chameleon-client/src/views/CameraViewes/InputTab.vue +++ b/chameleon-client/src/views/CameraViewes/InputTab.vue @@ -2,7 +2,7 @@
-
diff --git a/tets/src/main/java/com/animal.java b/tets/src/main/java/com/animal.java new file mode 100644 index 000000000..dc4ec5401 --- /dev/null +++ b/tets/src/main/java/com/animal.java @@ -0,0 +1,4 @@ +public class animal{ + String name; +} + diff --git a/tets/src/main/java/com/cat.java b/tets/src/main/java/com/cat.java new file mode 100644 index 000000000..820e617d9 --- /dev/null +++ b/tets/src/main/java/com/cat.java @@ -0,0 +1,3 @@ +public class cat extends animal{ + String meow = "mowe"; +} diff --git a/tets/src/main/java/com/cow.java b/tets/src/main/java/com/cow.java new file mode 100644 index 000000000..4c7c70c16 --- /dev/null +++ b/tets/src/main/java/com/cow.java @@ -0,0 +1,3 @@ +public class cow extends animal{ + String moo = "mooo"; +} diff --git a/tets/src/main/java/main.java b/tets/src/main/java/main.java new file mode 100644 index 000000000..ede4beca3 --- /dev/null +++ b/tets/src/main/java/main.java @@ -0,0 +1,4 @@ +package PACKAGE_NAME; + +public class main { +}