From 03dae15ddfbb01c31c457756bc390359e8e32c87 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Sun, 13 Jan 2019 16:59:08 -0800 Subject: [PATCH] Add stream settings to webdash and examples (#61) --- deps/examples/cpp-multiCameraServer/main.cpp | 22 ++- .../src/main/java/Main.java | 25 +++- .../multiCameraServer.py | 23 ++- deps/robotpy-cscore | 2 +- deps/tools/multiCameraServer.cpp | 22 ++- .../resources/frcvision.js | 54 ++++++- .../rpiConfigServer_src/resources/index.html | 136 +++++++++++------- 7 files changed, 220 insertions(+), 64 deletions(-) diff --git a/deps/examples/cpp-multiCameraServer/main.cpp b/deps/examples/cpp-multiCameraServer/main.cpp index 7a6e1b5..fc7c35f 100644 --- a/deps/examples/cpp-multiCameraServer/main.cpp +++ b/deps/examples/cpp-multiCameraServer/main.cpp @@ -41,7 +41,15 @@ "name": "value": } - ] + ], + "stream": { // optional + "properties": [ + { + "name": + "value": + } + ] + } } ] } @@ -58,6 +66,7 @@ struct CameraConfig { std::string name; std::string path; wpi::json config; + wpi::json streamConfig; }; std::vector cameraConfigs; @@ -86,6 +95,9 @@ bool ReadCameraConfig(const wpi::json& config) { return false; } + // stream properties + if (config.count("stream") != 0) c.streamConfig = config.at("stream"); + c.config = config; cameraConfigs.emplace_back(std::move(c)); @@ -158,12 +170,16 @@ bool ReadConfig() { cs::UsbCamera StartCamera(const CameraConfig& config) { wpi::outs() << "Starting camera '" << config.name << "' on " << config.path << '\n'; - auto camera = frc::CameraServer::GetInstance()->StartAutomaticCapture( - config.name, config.path); + cs::UsbCamera camera{config.name, config.path}; + auto inst = frc::CameraServer::GetInstance(); + auto server = inst->StartAutomaticCapture(camera); camera.SetConfigJson(config.config); camera.SetConnectionStrategy(cs::VideoSource::kConnectionKeepOpen); + if (config.streamConfig.is_object()) + server.SetConfigJson(config.streamConfig); + return camera; } diff --git a/deps/examples/java-multiCameraServer/src/main/java/Main.java b/deps/examples/java-multiCameraServer/src/main/java/Main.java index b63aa72..5f7c061 100644 --- a/deps/examples/java-multiCameraServer/src/main/java/Main.java +++ b/deps/examples/java-multiCameraServer/src/main/java/Main.java @@ -18,6 +18,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import edu.wpi.cscore.MjpegServer; +import edu.wpi.cscore.UsbCamera; import edu.wpi.cscore.VideoSource; import edu.wpi.first.cameraserver.CameraServer; import edu.wpi.first.networktables.NetworkTableInstance; @@ -47,7 +49,15 @@ import org.opencv.core.Mat; "name": "value": } - ] + ], + "stream": { // optional + "properties": [ + { + "name": + "value": + } + ] + } } ] } @@ -61,6 +71,7 @@ public final class Main { public String name; public String path; public JsonObject config; + public JsonElement streamConfig; } public static int team; @@ -99,6 +110,9 @@ public final class Main { } cam.path = pathElement.getAsString(); + // stream properties + cam.streamConfig = config.get("stream"); + cam.config = config; cameraConfigs.add(cam); @@ -167,14 +181,19 @@ public final class Main { */ public static VideoSource startCamera(CameraConfig config) { System.out.println("Starting camera '" + config.name + "' on " + config.path); - VideoSource camera = CameraServer.getInstance().startAutomaticCapture( - config.name, config.path); + UsbCamera camera = new UsbCamera(config.name, config.path); + CameraServer inst = CameraServer.getInstance(); + MjpegServer server = inst.startAutomaticCapture(camera); Gson gson = new GsonBuilder().create(); camera.setConfigJson(gson.toJson(config.config)); camera.setConnectionStrategy(VideoSource.ConnectionStrategy.kKeepOpen); + if (config.streamConfig != null) { + server.setConfigJson(gson.toJson(config.streamConfig)); + } + return camera; } diff --git a/deps/examples/python-multiCameraServer/multiCameraServer.py b/deps/examples/python-multiCameraServer/multiCameraServer.py index 17b1059..d997581 100755 --- a/deps/examples/python-multiCameraServer/multiCameraServer.py +++ b/deps/examples/python-multiCameraServer/multiCameraServer.py @@ -10,7 +10,7 @@ import json import time import sys -from cscore import CameraServer, VideoSource +from cscore import CameraServer, VideoSource, UsbCamera, MjpegServer from networktables import NetworkTablesInstance # JSON format: @@ -33,7 +33,15 @@ from networktables import NetworkTablesInstance # "name": # "value": # } -# ] +# ], +# "stream": { // optional +# "properties": [ +# { +# "name": +# "value": +# } +# ] +# } # } # ] # } @@ -68,6 +76,9 @@ def readCameraConfig(config): parseError("camera '{}': could not read path".format(cam.name)) return False + # stream properties + cam.streamConfig = config.get("stream") + cam.config = config cameraConfigs.append(cam) @@ -123,12 +134,16 @@ def readConfig(): """Start running the camera.""" def startCamera(config): print("Starting camera '{}' on {}".format(config.name, config.path)) - camera = CameraServer.getInstance() \ - .startAutomaticCapture(name=config.name, path=config.path) + camera = UsbCamera(config.name, config.path) + inst = CameraServer.getInstance() + server = inst.startAutomaticCapture(camera=camera, return_server=True) camera.setConfigJson(json.dumps(config.config)) camera.setConnectionStrategy(VideoSource.ConnectionStrategy.kKeepOpen) + if config.streamConfig is not None: + server.setConfigJson(json.dumps(config.streamConfig)) + return camera if __name__ == "__main__": diff --git a/deps/robotpy-cscore b/deps/robotpy-cscore index 64dbe28..2590274 160000 --- a/deps/robotpy-cscore +++ b/deps/robotpy-cscore @@ -1 +1 @@ -Subproject commit 64dbe28376f7a20ced32a7b26ada4b59ea31ae5f +Subproject commit 2590274ca4e9ef852093152be34bde4e56d027f3 diff --git a/deps/tools/multiCameraServer.cpp b/deps/tools/multiCameraServer.cpp index 8ed541e..2aea5eb 100644 --- a/deps/tools/multiCameraServer.cpp +++ b/deps/tools/multiCameraServer.cpp @@ -38,7 +38,15 @@ "name": "value": } - ] + ], + "stream": { // optional + "properties": [ + { + "name": + "value": + } + ] + } } ] } @@ -59,6 +67,7 @@ struct CameraConfig { std::string name; std::string path; wpi::json config; + wpi::json streamConfig; }; std::vector cameras; @@ -87,6 +96,9 @@ bool ReadCameraConfig(const wpi::json& config) { return false; } + // stream properties + if (config.count("stream") != 0) c.streamConfig = config.at("stream"); + c.config = config; cameras.emplace_back(std::move(c)); @@ -159,11 +171,15 @@ bool ReadConfig() { void StartCamera(const CameraConfig& config) { wpi::outs() << "Starting camera '" << config.name << "' on " << config.path << '\n'; - auto camera = frc::CameraServer::GetInstance()->StartAutomaticCapture( - config.name, config.path); + cs::UsbCamera camera{config.name, config.path}; + auto inst = frc::CameraServer::GetInstance(); + auto server = inst->StartAutomaticCapture(camera); camera.SetConfigJson(config.config); camera.SetConnectionStrategy(cs::VideoSource::kConnectionKeepOpen); + + if (config.streamConfig.is_object()) + server.SetConfigJson(config.streamConfig); } } // namespace diff --git a/deps/tools/rpiConfigServer_src/resources/frcvision.js b/deps/tools/rpiConfigServer_src/resources/frcvision.js index 5b4a953..2c74a94 100644 --- a/deps/tools/rpiConfigServer_src/resources/frcvision.js +++ b/deps/tools/rpiConfigServer_src/resources/frcvision.js @@ -41,7 +41,7 @@ function displayStatus(message) { // Enable and disable buttons based on connection status var connectedButtonIds = ['systemRestart', 'networkApproach', 'networkAddress', 'networkMask', 'networkGateway', 'networkDNS', 'visionUp', 'visionDown', 'visionTerm', 'visionKill', 'systemReadOnly', 'systemWritable', 'visionClient', 'visionTeam', 'visionDiscard', 'addConnectedCamera', 'addCamera', 'applicationType']; -var connectedButtonClasses = ['cameraName', 'cameraPath', 'cameraAlternatePaths', 'cameraPixelFormat', 'cameraWidth', 'cameraHeight', 'cameraFps', 'cameraBrightness', 'cameraWhiteBalance', 'cameraExposure', 'cameraProperties', 'cameraRemove', 'cameraCopyConfig'] +var connectedButtonClasses = ['cameraName', 'cameraPath', 'cameraAlternatePaths', 'cameraPixelFormat', 'cameraWidth', 'cameraHeight', 'cameraFps', 'cameraBrightness', 'cameraWhiteBalance', 'cameraExposure', 'cameraProperties', 'streamWidth', 'streamHeight', 'streamFps', 'streamCompression', 'streamDefaultCompression', 'cameraRemove', 'cameraCopyConfig'] var writableButtonIds = ['networkSave', 'visionSave', 'applicationSave']; var systemStatusIds = ['systemMemoryFree1s', 'systemMemoryFree5s', 'systemMemoryAvail1s', 'systemMemoryAvail5s', @@ -340,6 +340,18 @@ $('#visionClient').change(function() { } }); +function getCameraPropertyValue(data, name) { + if (data === null) { + return null; + } + for (var i = 0; i < data.length; i++) { + if (data[i].name === name) { + return data[i].value; + } + } + return null; +} + function updateVisionCameraView(camera, value) { if ('name' in value) { camera.find('.cameraTitle').text('Camera ' + value.name); @@ -356,6 +368,19 @@ function updateVisionCameraView(camera, value) { camera.find('.cameraWhiteBalance').val(value['white balance']); camera.find('.cameraExposure').val(value.exposure); camera.find('.cameraProperties').val(JSON.stringify(value.properties)); + if ('stream' in value && 'properties' in value.stream) { + camera.find('.streamWidth').val(getCameraPropertyValue(value.stream.properties, 'width')); + camera.find('.streamHeight').val(getCameraPropertyValue(value.stream.properties, 'height')); + camera.find('.streamFps').val(getCameraPropertyValue(value.stream.properties, 'fps')); + camera.find('.streamCompression').val(getCameraPropertyValue(value.stream.properties, 'compression')); + camera.find('.streamDefaultCompression').val(getCameraPropertyValue(value.stream.properties, 'default_compression')); + } else { + camera.find('.streamWidth').val(''); + camera.find('.streamHeight').val(''); + camera.find('.streamFps').val(''); + camera.find('.streamCompression').val(''); + camera.find('.streamDefaultCompression').val(''); + } } function updateVisionCameraDataFromJson(i, data) { @@ -548,6 +573,33 @@ $('#visionSave').click(function() { } catch (err) { delete value['properties']; } + + value.stream = {'properties': []}; + + var streamWidth = parseInt(camera.find('.streamWidth').val(), 10); + if (!isNaN(streamWidth)) { + value.stream.properties.push({'name': 'width', 'value': streamWidth}); + } + + var streamHeight = parseInt(camera.find('.streamHeight').val(), 10); + if (!isNaN(streamHeight)) { + value.stream.properties.push({'name': 'height', 'value': streamHeight}); + } + + var streamFps = parseInt(camera.find('.streamFps').val(), 10); + if (!isNaN(streamFps)) { + value.stream.properties.push({'name': 'fps', 'value': streamFps}); + } + + var streamCompression = parseInt(camera.find('.streamCompression').val(), 10); + if (!isNaN(streamCompression)) { + value.stream.properties.push({'name': 'compression', 'value': streamCompression}); + } + + var streamDefaultCompression = parseInt(camera.find('.streamDefaultCompression').val(), 10); + if (!isNaN(streamDefaultCompression)) { + value.stream.properties.push({'name': 'default_compression', 'value': streamDefaultCompression}); + } }); var msg = { type: 'visionSave', diff --git a/deps/tools/rpiConfigServer_src/resources/index.html b/deps/tools/rpiConfigServer_src/resources/index.html index 9e0f919..c3e848d 100644 --- a/deps/tools/rpiConfigServer_src/resources/index.html +++ b/deps/tools/rpiConfigServer_src/resources/index.html @@ -280,59 +280,97 @@ -
-
- - +
+
+ Camera Settings +
+
+
+
+ + +
+
+
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
-
- -
-
-
- - +
+
+ Video Stream Defaults
-
- - +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
- -