Merge "Build a basic rootfs for the rockpi 4b"
diff --git a/BUILD b/BUILD
index 9368a1f..4a4baa5 100644
--- a/BUILD
+++ b/BUILD
@@ -32,6 +32,8 @@
# gazelle:resolve go github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response //scouting/webserver/requests/messages:request_shift_schedule_response_go_fbs
# gazelle:resolve go github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule //scouting/webserver/requests/messages:submit_shift_schedule_go_fbs
# gazelle:resolve go github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response //scouting/webserver/requests/messages:submit_shift_schedule_response_go_fbs
+# gazelle:resolve go github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking //scouting/webserver/requests/messages:submit_driver_ranking_go_fbs
+# gazelle:resolve go github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response //scouting/webserver/requests/messages:submit_driver_ranking_response_go_fbs
gazelle(
name = "gazelle",
diff --git a/WORKSPACE b/WORKSPACE
index 172e7bb..dbde661 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -56,6 +56,10 @@
python_gtk_debs = "files",
)
load(
+ "//debian:gtk_runtime.bzl",
+ gtk_runtime_debs = "files",
+)
+load(
"//debian:opencv_arm64.bzl",
opencv_arm64_debs = "files",
)
@@ -127,6 +131,8 @@
generate_repositories_for_debs(python_gtk_debs)
+generate_repositories_for_debs(gtk_runtime_debs)
+
generate_repositories_for_debs(opencv_arm64_debs)
generate_repositories_for_debs(opencv_armhf_debs)
@@ -683,6 +689,13 @@
url = "https://www.frc971.org/Build-Dependencies/python_gtk-4.tar.gz",
)
+http_archive(
+ name = "gtk_runtime",
+ build_file = "@//debian:gtk_runtime.BUILD",
+ sha256 = "934693e64bfe63f0c55cdf432fe183eb077d6875d4d6a3dce4e47dbe7e20f5a9",
+ url = "https://www.frc971.org/Build-Dependencies/gtk_runtime-3.tar.gz",
+)
+
# Downloaded from
# https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2018q2/gcc-arm-none-eabi-7-2018-q2-update-linux.tar.bz2?revision=bc2c96c0-14b5-4bb4-9f18-bceb4050fee7?product=GNU%20Arm%20Embedded%20Toolchain,64-bit,,Linux,7-2018-q2-update
http_archive(
diff --git a/aos/BUILD b/aos/BUILD
index 596996b..164f40b 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -514,6 +514,7 @@
"configuration_test.cc",
],
data = [
+ "//aos/events:ping_fbs_reflection_out",
"//aos/events:pingpong_config",
"//aos/events:pong_fbs_reflection_out",
"//aos/testdata:test_configs",
@@ -521,6 +522,7 @@
target_compatible_with = ["@platforms//os:linux"],
deps = [
":configuration",
+ "//aos/events:ping_fbs",
"//aos/testing:flatbuffer_eq",
"//aos/testing:googletest",
"//aos/testing:path",
diff --git a/aos/configuration.cc b/aos/configuration.cc
index d5ac2a1..ad6fb54 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -68,7 +68,6 @@
}
} // namespace
-
// Define the compare and equal operators for Channel and Application so we can
// insert them in the btree below.
bool operator<(const FlatbufferDetachedBuffer<Channel> &lhs,
@@ -1594,5 +1593,30 @@
return channel->num_readers() + channel->num_senders();
}
+// Searches through configurations for schemas that include a certain type
+const reflection::Schema *GetSchema(const Configuration *config,
+ std::string_view schema_type) {
+ if (config->has_channels()) {
+ std::vector<flatbuffers::Offset<Channel>> channel_offsets;
+ for (const Channel *c : *config->channels()) {
+ if (schema_type == c->type()->string_view()) {
+ return c->schema();
+ }
+ }
+ }
+ return nullptr;
+}
+
+// Copy schema reflection into detached flatbuffer
+std::optional<FlatbufferDetachedBuffer<reflection::Schema>>
+GetSchemaDetachedBuffer(const Configuration *config,
+ std::string_view schema_type) {
+ const reflection::Schema *found_schema = GetSchema(config, schema_type);
+ if (found_schema == nullptr) {
+ return std::nullopt;
+ }
+ return RecursiveCopyFlatBuffer(found_schema);
+}
+
} // namespace configuration
} // namespace aos
diff --git a/aos/configuration.h b/aos/configuration.h
index 8515b18..4d84b23 100644
--- a/aos/configuration.h
+++ b/aos/configuration.h
@@ -212,7 +212,20 @@
// Returns the number of scratch buffers in the queue.
int QueueScratchBufferSize(const Channel *channel);
-// TODO(austin): GetSchema<T>(const Flatbuffer<Configuration> &config);
+// Searches through configurations for schemas that include a certain type.
+const reflection::Schema *GetSchema(const Configuration *config,
+ std::string_view schema_type);
+
+// GetSchema template
+template <typename T>
+const reflection::Schema *GetSchema(const Configuration *config) {
+ return GetSchema(config, T::GetFullyQualifiedName());
+}
+
+// Copy schema reflection into detached flatbuffer
+std::optional<FlatbufferDetachedBuffer<reflection::Schema>>
+GetSchemaDetachedBuffer(const Configuration *config,
+ std::string_view schema_type);
} // namespace configuration
@@ -222,6 +235,7 @@
const FlatbufferDetachedBuffer<Channel> &rhs);
bool operator==(const FlatbufferDetachedBuffer<Channel> &lhs,
const FlatbufferDetachedBuffer<Channel> &rhs);
+
} // namespace aos
#endif // AOS_CONFIGURATION_H_
diff --git a/aos/configuration_test.cc b/aos/configuration_test.cc
index fa74e20..fc45d88 100644
--- a/aos/configuration_test.cc
+++ b/aos/configuration_test.cc
@@ -1,6 +1,7 @@
#include "aos/configuration.h"
#include "absl/strings/strip.h"
+#include "aos/events/ping_generated.h"
#include "aos/json_to_flatbuffer.h"
#include "aos/testing/flatbuffer_eq.h"
#include "aos/testing/path.h"
@@ -1007,10 +1008,47 @@
JsonToFlatbuffer<Channel>(
"{ \"name\": \"/foo\", \"type\": \".aos.bar\", \"num_readers\": 5, "
"\"num_senders\": 10 }");
-
EXPECT_EQ(QueueScratchBufferSize(&channel.message()), 15);
}
+// Tests that GetSchema returns schema of specified type
+TEST_F(ConfigurationTest, GetSchema) {
+ FlatbufferDetachedBuffer<Configuration> config =
+ ReadConfig(ArtifactPath("aos/events/pingpong_config.json"));
+ FlatbufferVector<reflection::Schema> expected_schema =
+ FileToFlatbuffer<reflection::Schema>(
+ ArtifactPath("aos/events/ping.bfbs"));
+ EXPECT_EQ(FlatbufferToJson(GetSchema(&config.message(), "aos.examples.Ping")),
+ FlatbufferToJson(expected_schema));
+ EXPECT_EQ(GetSchema(&config.message(), "invalid_name"), nullptr);
+}
+
+// Tests that GetSchema template returns schema of specified type
+TEST_F(ConfigurationTest, GetSchemaTemplate) {
+ FlatbufferDetachedBuffer<Configuration> config =
+ ReadConfig(ArtifactPath("aos/events/pingpong_config.json"));
+ FlatbufferVector<reflection::Schema> expected_schema =
+ FileToFlatbuffer<reflection::Schema>(
+ ArtifactPath("aos/events/ping.bfbs"));
+ EXPECT_EQ(FlatbufferToJson(GetSchema<aos::examples::Ping>(&config.message())),
+ FlatbufferToJson(expected_schema));
+}
+
+// Tests that GetSchemaDetachedBuffer returns detached buffer of specified type
+TEST_F(ConfigurationTest, GetSchemaDetachedBuffer) {
+ FlatbufferDetachedBuffer<Configuration> config =
+ ReadConfig(ArtifactPath("aos/events/pingpong_config.json"));
+ FlatbufferVector<reflection::Schema> expected_schema =
+ FileToFlatbuffer<reflection::Schema>(
+ ArtifactPath("aos/events/ping.bfbs"));
+ EXPECT_EQ(FlatbufferToJson(
+ GetSchemaDetachedBuffer(&config.message(), "aos.examples.Ping")
+ .value()),
+ FlatbufferToJson(expected_schema));
+ EXPECT_EQ(GetSchemaDetachedBuffer(&config.message(), "invalid_name"),
+ std::nullopt);
+}
+
} // namespace testing
} // namespace configuration
} // namespace aos
diff --git a/aos/events/event_loop_runtime.rs b/aos/events/event_loop_runtime.rs
index 360c931..023cfb6 100644
--- a/aos/events/event_loop_runtime.rs
+++ b/aos/events/event_loop_runtime.rs
@@ -370,6 +370,9 @@
MonotonicInstant(self.0.monotonic_now())
}
+ pub fn realtime_now(&self) -> RealtimeInstant {
+ RealtimeInstant(self.0.realtime_now())
+ }
/// Note that the `'event_loop` input lifetime is intentional. The C++ API requires that it is
/// part of `self.configuration()`, which will always have this lifetime.
///
@@ -711,7 +714,6 @@
where
T: Follow<'a> + 'a;
-// TODO(Brian): Add the realtime timestamps here.
impl<'a, T> TypedContext<'a, T>
where
T: Follow<'a> + 'a,
@@ -730,6 +732,12 @@
pub fn monotonic_remote_time(&self) -> MonotonicInstant {
self.0.monotonic_remote_time()
}
+ pub fn realtime_event_time(&self) -> RealtimeInstant {
+ self.0.realtime_event_time()
+ }
+ pub fn realtime_remote_time(&self) -> RealtimeInstant {
+ self.0.realtime_remote_time()
+ }
pub fn queue_index(&self) -> u32 {
self.0.queue_index()
}
@@ -750,10 +758,11 @@
T::Inner: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- // TODO(Brian): Add the realtime timestamps here.
f.debug_struct("TypedContext")
.field("monotonic_event_time", &self.monotonic_event_time())
.field("monotonic_remote_time", &self.monotonic_remote_time())
+ .field("realtime_event_time", &self.realtime_event_time())
+ .field("realtime_remote_time", &self.realtime_remote_time())
.field("queue_index", &self.queue_index())
.field("remote_queue_index", &self.remote_queue_index())
.field("message", &self.message())
@@ -1020,10 +1029,11 @@
impl fmt::Debug for Context<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- // TODO(Brian): Add the realtime timestamps here.
f.debug_struct("Context")
.field("monotonic_event_time", &self.monotonic_event_time())
.field("monotonic_remote_time", &self.monotonic_remote_time())
+ .field("realtime_event_time", &self.realtime_event_time())
+ .field("realtime_remote_time", &self.realtime_remote_time())
.field("queue_index", &self.queue_index())
.field("remote_queue_index", &self.remote_queue_index())
.field("size", &self.data().map(|data| data.len()))
@@ -1033,7 +1043,6 @@
}
}
-// TODO(Brian): Add the realtime timestamps here.
impl<'context> Context<'context> {
pub fn monotonic_event_time(self) -> MonotonicInstant {
MonotonicInstant(self.0.monotonic_event_time)
@@ -1043,6 +1052,14 @@
MonotonicInstant(self.0.monotonic_remote_time)
}
+ pub fn realtime_event_time(self) -> RealtimeInstant {
+ RealtimeInstant(self.0.realtime_event_time)
+ }
+
+ pub fn realtime_remote_time(self) -> RealtimeInstant {
+ RealtimeInstant(self.0.realtime_remote_time)
+ }
+
pub fn queue_index(self) -> u32 {
self.0.queue_index
}
@@ -1093,9 +1110,6 @@
/// Represents a `aos::monotonic_clock::time_point` in a natural Rust way. This
/// is intended to have the same API as [`std::time::Instant`], any missing
/// functionality can be added if useful.
-///
-/// TODO(Brian): Do RealtimeInstant too. Use a macro? Integer as a generic
-/// parameter to distinguish them? Or just copy/paste?
#[repr(transparent)]
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct MonotonicInstant(i64);
@@ -1125,6 +1139,34 @@
}
}
+#[repr(transparent)]
+#[derive(Clone, Copy, Eq, PartialEq)]
+pub struct RealtimeInstant(i64);
+
+impl RealtimeInstant {
+ pub const MIN_TIME: Self = Self(i64::MIN);
+
+ pub fn is_min_time(self) -> bool {
+ self == Self::MIN_TIME
+ }
+
+ pub fn duration_since_epoch(self) -> Option<Duration> {
+ if self.is_min_time() {
+ None
+ } else {
+ Some(Duration::from_nanos(self.0.try_into().expect(
+ "monotonic_clock::time_point should always be after the epoch",
+ )))
+ }
+ }
+}
+
+impl fmt::Debug for RealtimeInstant {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.duration_since_epoch().fmt(f)
+ }
+}
+
mod panic_waker {
use std::task::{RawWaker, RawWakerVTable, Waker};
diff --git a/aos/util/file.cc b/aos/util/file.cc
index 317206e..cdf0061 100644
--- a/aos/util/file.cc
+++ b/aos/util/file.cc
@@ -36,7 +36,6 @@
void WriteStringToFileOrDie(const std::string_view filename,
const std::string_view contents,
mode_t permissions) {
- ::std::string r;
ScopedFD fd(open(::std::string(filename).c_str(),
O_CREAT | O_WRONLY | O_TRUNC, permissions));
PCHECK(fd.get() != -1) << ": opening " << filename;
diff --git a/debian/BUILD b/debian/BUILD
index de5144d..f14db44 100644
--- a/debian/BUILD
+++ b/debian/BUILD
@@ -51,6 +51,10 @@
python_gtk_debs = "files",
)
load(
+ ":gtk_runtime.bzl",
+ gtk_runtime_debs = "files",
+)
+load(
":opencv_arm64.bzl",
opencv_arm64_debs = "files",
)
@@ -156,6 +160,33 @@
)
download_packages(
+ name = "download_gtk_runtime",
+ excludes = [
+ "libstdc++6",
+ "lsb-base",
+ "libglib2.0-dev-bin",
+ "fonts-freefont",
+ "gsettings-backend",
+ "libpng-dev",
+ "libz-dev",
+ "libstdc++-dev",
+ "libc6-dev",
+ ],
+ # Since "libglib2.0-0" pulls in glibc, we need to forcibly remove it again.
+ force_excludes = [
+ "libc6",
+ "libgcc-s1",
+ ],
+ force_includes = [
+ "libglib2.0-0",
+ ],
+ packages = [
+ "gir1.2-gtk-3.0",
+ "libgtk-3-dev",
+ ],
+)
+
+download_packages(
name = "download_python_deps",
excludes = [
"libblas.so.3",
@@ -390,6 +421,12 @@
target_compatible_with = ["@platforms//os:linux"],
)
+generate_deb_tarball(
+ name = "gtk_runtime",
+ files = gtk_runtime_debs,
+ target_compatible_with = ["@platforms//os:linux"],
+)
+
download_packages(
name = "download_opencv",
packages = [
diff --git a/debian/download_packages.py b/debian/download_packages.py
index 6d548b5..d012ff6 100755
--- a/debian/download_packages.py
+++ b/debian/download_packages.py
@@ -105,12 +105,15 @@
yield package
-def download_deps(apt_args, packages, excludes, force_includes):
+def download_deps(apt_args, packages, excludes, force_includes,
+ force_excludes):
deps = get_all_deps(apt_args, packages)
exclude_deps = get_all_deps(apt_args, excludes)
deps -= exclude_deps
force_include_deps = get_all_deps(apt_args, force_includes)
deps |= force_include_deps
+ force_exclude_deps = get_all_deps(apt_args, force_excludes)
+ deps -= force_exclude_deps
env = dict(os.environ)
del env['LD_LIBRARY_PATH']
subprocess.check_call([b"apt-get"] + [a.encode('utf-8')
@@ -173,6 +176,13 @@
action="append",
help=
"Force include this and its dependencies. Even if listed in excludes.")
+ parser.add_argument(
+ "--force-exclude",
+ type=str,
+ action="append",
+ help=
+ "Force exclude this and its dependencies. Even if listed via --force-include."
+ )
parser.add_argument("--arch",
type=str,
default="amd64",
@@ -214,7 +224,8 @@
# Exclude common packages that don't make sense to include in everything all
# the time.
excludes += _ALWAYS_EXCLUDE
- download_deps(apt_args, args.package, excludes, args.force_include)
+ download_deps(apt_args, args.package, excludes, args.force_include,
+ args.force_exclude)
fixup_files()
print_file_list()
print("Your packages are all in %s" % folder)
diff --git a/debian/gtk_runtime.BUILD b/debian/gtk_runtime.BUILD
new file mode 100644
index 0000000..e529782
--- /dev/null
+++ b/debian/gtk_runtime.BUILD
@@ -0,0 +1,12 @@
+filegroup(
+ name = "gtk_runtime",
+ srcs = glob([
+ "etc/**",
+ "lib/x86_64-linux-gnu/**/*.so*",
+ "usr/lib/**/*.so*",
+ "usr/lib/x86_64-linux-gnu/**/*",
+ "usr/share/font*/**",
+ "usr/share/gir-1.0/**",
+ ]),
+ visibility = ["//visibility:public"],
+)
diff --git a/debian/gtk_runtime.bzl b/debian/gtk_runtime.bzl
new file mode 100644
index 0000000..029419c
--- /dev/null
+++ b/debian/gtk_runtime.bzl
@@ -0,0 +1,267 @@
+files = {
+ "adwaita-icon-theme_3.38.0-1_all.deb": "2046876c82fc1c342b38ace9aa0661bcb3e167837c984b4bdc89702bc78df5ac",
+ "coreutils_8.32-4+b1_amd64.deb": "3558a412ab51eee4b60641327cb145bb91415f127769823b68f9335585b308d4",
+ "dconf-gsettings-backend_0.38.0-2_amd64.deb": "194991ed5f4ab1ca25413858cb99c910391cfd6d3b1b6a3d3e56a4b3a706a37d",
+ "dconf-service_0.38.0-2_amd64.deb": "639125f7a44d11f96661c61a07abbb58da0e5636ed406ac186adcef8651775c2",
+ "fontconfig-config_2.13.1-4.2_all.deb": "48afb6ad7d15e6104a343b789f73697301ad8bff77b69927bc998f5a409d8e90",
+ "fontconfig_2.13.1-4.2_amd64.deb": "c594a100759ef7c94149359cf4d2da5fb59ef30474c7a2dde1e049d32b9c478a",
+ "fonts-croscore_20201225-1_all.deb": "64904820b729ff40038f85683004e3b94b328d969bc0fbba263c58d635452923",
+ "fonts-dejavu-core_2.37-2_all.deb": "1f67421437b6eb18669d2868e3e02cb88668683d635198142f48aacc5b397118",
+ "fonts-freefont-otf_20120503-10_all.deb": "0b63996c80c6c660424af6d3832818e647960d6f65a51de010bb57dd0762faa7",
+ "fonts-freefont-ttf_20120503-10_all.deb": "4ca1c21ebc479198a3a5879d236c8317d6f7b2f1c403f7890e24c02eead05615",
+ "fonts-liberation2_2.1.3-1_all.deb": "e0805f0085132f5e6dd30f88c0d7260caf1e5450832fe2e3988a20fa9fa2150e",
+ "fonts-liberation_1.07.4-11_all.deb": "efd381517f958b01969343634ffcbdd60056be7779af84c6f53a005090430204",
+ "fonts-texgyre_20180621-3.1_all.deb": "cb7e9a4b2471cfdd57194c16364f9102f0639816a2662fed4b30d2a158747076",
+ "fonts-urw-base35_20200910-1_all.deb": "f95a139adb7f1b60626e76d4d45d1b35aad1bc2c2597394c291ef5f84b5dcb43",
+ "gir1.2-atk-1.0_2.36.0-2_amd64.deb": "36154c1e50e8e8013a14ce4ecfa1cf7527250beb49a2c60ac02ab2c8a40a5357",
+ "gir1.2-atspi-2.0_2.38.0-4_amd64.deb": "2b6f6d4c3de060e4f52cb7edba4c6ed9ab8d3601c4be617feac12e042df873ca",
+ "gir1.2-freedesktop_1.66.1-1+b1_amd64.deb": "60d8f35f0d67548088525543e3ff9e00934ebb5bfe7639afa45e5740e024f991",
+ "gir1.2-gdkpixbuf-2.0_2.42.2+dfsg-1+deb11u1_amd64.deb": "ba2552eb10b14b6f8ab44e28b7d638dd28de5b7e9a593e08b6f69395382a5c7b",
+ "gir1.2-glib-2.0_1.66.1-1+b1_amd64.deb": "1163a4e7eb095e37752739c0065bad50fa2177c13a87e7c1b0d44ed517fe8c91",
+ "gir1.2-gtk-3.0_3.24.24-4+deb11u2_amd64.deb": "a2a5c8e5aa3d8f7f5244464afce826485c96a0a3f2af8380fb33d8f2b2eb550d",
+ "gir1.2-harfbuzz-0.0_2.7.4-1_amd64.deb": "057b61d69437910e0350076cc0dd46d3ddb01ba181a434802aa328e81bc440d1",
+ "gir1.2-pango-1.0_1.46.2-3_amd64.deb": "0859356937e4b269201341ce410c77761fb68537ed3c317c223e7e67105ab0bb",
+ "glib-networking-common_2.66.0-2_all.deb": "a07370151ce5169e48ee7799b9bd9a7a035467a21f5cf3373b2aff090968609c",
+ "glib-networking-services_2.66.0-2_amd64.deb": "19131c7c31bc3fae604df30d2f73c3e8338ffeb2988fe167bb8b2b1c8913c9ab",
+ "glib-networking_2.66.0-2_amd64.deb": "b2cd50a8c3b30c16fd1a19c5244f681c6c0d1f426c385d44900477b052f70024",
+ "gsettings-desktop-schemas_3.38.0-2_all.deb": "3758968491a770e50cd85122c00d141944ffb43cb7a5c886a37290fef848cee3",
+ "gtk-update-icon-cache_3.24.24-4+deb11u2_amd64.deb": "b877617f382240663be1010510511a5f9fe10853a3f97088cc01be277ff184d6",
+ "hicolor-icon-theme_0.17-2_all.deb": "20304d34b85a734ec1e4830badf3a3a70a5dc5f9c1afc0b2230ecd760c81b5e0",
+ "icu-devtools_67.1-7_amd64.deb": "0a89d6f360d9c686c08d0156a0c8244715c9aaeffca079cf1716f12cffece82e",
+ "libatk-bridge2.0-0_2.38.0-1_amd64.deb": "65b063b4b45c5fd60d91e374d01bb73eacdb30c545a6ef0873d07d6da97765d1",
+ "libatk-bridge2.0-dev_2.38.0-1_amd64.deb": "04be11ea79e542a4eea20977e23557c5cc21427e93c354d69b86586f81d248c7",
+ "libatk1.0-0_2.36.0-2_amd64.deb": "572cd62f92ec25c75b98617321373d46a6717cbcc93d2025ebd6d550f1abf901",
+ "libatk1.0-data_2.36.0-2_all.deb": "86c1acae473977f8a78b905090847df654306996324493f9a39d9f27807778b2",
+ "libatk1.0-dev_2.36.0-2_amd64.deb": "8a107ce46427f5cf68076eb0ab7e9b09f0237cb2674499da582c5a29cfc94d72",
+ "libatspi2.0-0_2.38.0-4_amd64.deb": "53435278eb8815aafbb41db29a691a43a9de16fa58d9bc7908a1f6f2a07f0b67",
+ "libatspi2.0-dev_2.38.0-4_amd64.deb": "fbbb10ba97dbfc79c5c1edc223e606c792332a47d242b21b1dea5c9bae5dbc2c",
+ "libattr1_2.4.48-6_amd64.deb": "af3c3562eb2802481a2b9558df1b389f3c6d9b1bf3b4219e000e05131372ebaf",
+ "libavahi-client3_0.8-5+deb11u1_amd64.deb": "44104ae278d853f9d20b90a6192257497d430f3ff4af093af1c504effb9caf4f",
+ "libavahi-common-data_0.8-5+deb11u1_amd64.deb": "847c7050a234915514a967e2edbf8b1a02fe5451bb910f9bdeffda0688280fce",
+ "libavahi-common3_0.8-5+deb11u1_amd64.deb": "d5d97f84a894e6ef0e535a17d1dcc1ed64933d6e04a350306e989d05b37de00c",
+ "libblkid-dev_2.36.1-8+deb11u1_amd64.deb": "3f224b3dc4d094367b45b31c4bc367dd9528f45eba22af77229a7f9be7e6005d",
+ "libblkid1_2.36.1-8+deb11u1_amd64.deb": "9026ddd9f211008531ce6024d5ce042c723e237ecadfbf1f9343cb44aff492b9",
+ "libbrotli-dev_1.0.9-2+b2_amd64.deb": "520ef8f3af1a190ac2ce5954c0e42c8e6b80a593124f97e813be33e9e068ffc3",
+ "libbrotli1_1.0.9-2+b2_amd64.deb": "65ca7d8b03e9dac09c5d544a89dd52d1aeb74f6a19583d32e4ff5f0c77624c24",
+ "libbsd0_0.11.3-1_amd64.deb": "284a7b8dcfcad74770f57360721365317448b38ab773db542bf630e94e60c13e",
+ "libcairo-gobject2_1.16.0-5_amd64.deb": "a046d3ca805d4151029941fae736bfdf1c6f3dbcf1bd581102bd5ad844ea013e",
+ "libcairo-script-interpreter2_1.16.0-5_amd64.deb": "c1c47955283d36ccadbdfd88eef515063d28fdc20c751d70c863b18ca190ec8a",
+ "libcairo2-dev_1.16.0-5_amd64.deb": "a8ba01e9d19a1a4f512e7fa1ba1c089e2ace1a5b08733e167b9bea3fe86766de",
+ "libcairo2_1.16.0-5_amd64.deb": "b27210c0cf7757120e871abeba7de12a5cf94727a2360ecca5eb8e50ca809d12",
+ "libcolord2_1.4.5-3_amd64.deb": "b7f0b90535a04f25f4fe8a838b548eed87447b3225414bd4f30755ee917698dd",
+ "libcups2_2.3.3op2-3+deb11u2_amd64.deb": "67c6cf6ba6259468660da16676fcb7ac77cf4a14ca812a60375ca26263a7b273",
+ "libdatrie-dev_0.2.13-1_amd64.deb": "0885c9b6c0a448b1faaa5fa51f3b751b986f33e48ca98ae901413c22a4a6e5a3",
+ "libdatrie1_0.2.13-1_amd64.deb": "3544f2cf26039fade9c7e7297dde1458b8386442c3b0fc26fdf10127433341c1",
+ "libdbus-1-3_1.12.20-2_amd64.deb": "7256dfeda88461e6fccbf98372d3ec29487b3b2d0ae5d145a3332ab35274f0da",
+ "libdbus-1-dev_1.12.20-2_amd64.deb": "0bf0161cb23cf6d3adb3b7d5b701b982a65ad1ecff21e6267e69d803b1d88108",
+ "libdconf1_0.38.0-2_amd64.deb": "ff3b1d05466782acd6e335b001460b7af4ea76f49bbbbd5447535d2b702fa97e",
+ "libdeflate0_1.7-1_amd64.deb": "dadaf0d28360f6eb21ad389b2e0f12f8709c9de539b28de9c11d7ec7043dec95",
+ "libdpkg-perl_1.20.12_all.deb": "62b6da489682a684c8224a2cca0fc83d239846696cca5f67d5699c1df14b56ea",
+ "libdrm-amdgpu1_2.4.104-1_amd64.deb": "0005f21e342925bd26a25185289ae035aa931ced8f6fd9e3d4deade36d272ecd",
+ "libdrm-common_2.4.104-1_all.deb": "60c69026fb8e4cfdf8d80a4a86ee30516c611dcc4de4aa1c8ccbf06dff563e2b",
+ "libdrm-intel1_2.4.104-1_amd64.deb": "7d376adc7b5d4d83ec8414ff67dbc18765c6d420de9a6e1045fead7f1f82331d",
+ "libdrm-nouveau2_2.4.104-1_amd64.deb": "dbf4a3be55c609b1a2ea89d6782ae5c9a5b991844917dcd42c01666b73a96ceb",
+ "libdrm-radeon1_2.4.104-1_amd64.deb": "c33cd14e8ed7e2dfc02696ed51d4795c5797b0821666667e0a889bba705862b0",
+ "libdrm2_2.4.104-1_amd64.deb": "113396b3a33000f7f3347cd711ad9bcfe9945927331cc6cee63c751a889a967b",
+ "libedit2_3.1-20191231-2+b1_amd64.deb": "ac545f6ad10ba791aca24b09255ad1d6d943e6bc7c5511d5998e104aee51c943",
+ "libegl-dev_1.3.2-1_amd64.deb": "2847662b23487d5b1e467bca8cc8753baa880f794744a9b492c978bd5514b286",
+ "libegl-mesa0_20.3.5-1_amd64.deb": "a0c36a3665af89cbc96f865bd1b64c6c07b93096e91ba5b470d375d02dfa6d82",
+ "libegl1-mesa-dev_20.3.5-1_amd64.deb": "7b8139acb2e43a50fd952d54b41449baf13b404f65dccf187ae7852f028104f9",
+ "libegl1_1.3.2-1_amd64.deb": "3a5583ebd7a9d8ad102484db9637c409561428d21345037b310c4ef2ca8e8837",
+ "libelf1_0.183-1_amd64.deb": "e1ad132d502b255023c222d0cae1d02ca941f6b68fd0e9b908c6004cc326592c",
+ "libepoxy-dev_1.5.5-1_amd64.deb": "3979a7f81ffe10efcb1dcc3bd6e3ced5a88d1fe0ed68b12fb4cc4133b3e3d1b1",
+ "libepoxy0_1.5.5-1_amd64.deb": "3d050c9b138872c83b5b3521c97ab89f8a885b1391fdd0477cf8168ae54728a3",
+ "libexpat1-dev_2.2.10-2+deb11u4_amd64.deb": "fcf045732259c7303b8a2da0b2047e6823898cf3b0d7a40ece5dc6ad099a226a",
+ "libexpat1_2.2.10-2+deb11u4_amd64.deb": "d482f5d15353291e3a9e58c382d2ad3a412f028d3e553695a12f002c70b5a256",
+ "libffi-dev_3.3-6_amd64.deb": "ca2c71d9c68b1944b689606f12acf8023bad1b5083e8413894fd41ad0b977d20",
+ "libffi7_3.3-6_amd64.deb": "30ca89bfddae5fa6e0a2a044f22b6e50cd17c4bc6bc850c579819aeab7101f0f",
+ "libfontconfig-dev_2.13.1-4.2_amd64.deb": "7655d4238ee7e6ced13501006d20986cbf9ff08454a4e502d5aa399f83e28876",
+ "libfontconfig1-dev_2.13.1-4.2_amd64.deb": "a19502912fb57c1e9c87efbd7b7adad7f1c1b793164580ddf02168f0cfec59fb",
+ "libfontconfig1_2.13.1-4.2_amd64.deb": "b92861827627a76e74d6f447a5577d039ef2f95da18af1f29aa98fb96baea4c1",
+ "libfreetype-dev_2.10.4+dfsg-1+deb11u1_amd64.deb": "f0f5ece4c70fad68c8bdbe3cd09a5c9fb7d6112766f0d96439a45a9ae1aaf363",
+ "libfreetype6-dev_2.10.4+dfsg-1+deb11u1_amd64.deb": "ca1305fc7e3668f591ffc0e793569c7076dcf6d4765d8cf5cde7485fc7110beb",
+ "libfreetype6_2.10.4+dfsg-1+deb11u1_amd64.deb": "b21cfdd12adf6cac4af320c2485fb62a8a5edc6f9768bc2288fd686f4fa6dfdf",
+ "libfribidi-dev_1.0.8-2+deb11u1_amd64.deb": "b52de25f728d81b45440d5ad2458ca9809de700e8ee364320703d279e0ed4358",
+ "libfribidi0_1.0.8-2+deb11u1_amd64.deb": "690a889adfbe4e656e33b512dc1099cf29328632d684527dcfd4862810c5ee56",
+ "libgbm1_20.3.5-1_amd64.deb": "2d9b07282e46e3c9398613b6d4fe86c3259e4326b158be7e1f4f58cab541156c",
+ "libgcrypt20_1.8.7-6_amd64.deb": "7a2e0eef8e0c37f03f3a5fcf7102a2e3dc70ba987f696ab71949f9abf36f35ef",
+ "libgdk-pixbuf-2.0-0_2.42.2+dfsg-1+deb11u1_amd64.deb": "c593621089e9f8a8b5012de2cec9c835fdd64d0c42344423fc0a904c82e4967b",
+ "libgdk-pixbuf-2.0-dev_2.42.2+dfsg-1+deb11u1_amd64.deb": "9a0b3467e24b4369cffe154b55996d74270426b621748ae28044579c3a96e14b",
+ "libgdk-pixbuf-xlib-2.0-0_2.40.2-2_amd64.deb": "c11e9c92534e1e8036ad33a7ee1962b120834a02c41594cdf90ce01855ba84a4",
+ "libgdk-pixbuf-xlib-2.0-dev_2.40.2-2_amd64.deb": "5769f16c81c72ce50bd2aa8a7724b511d84ab411f9f23d5ef79b40fbd59c57e9",
+ "libgdk-pixbuf2.0-bin_2.42.2+dfsg-1+deb11u1_amd64.deb": "90ed931ec0abee05a6c8141ed6de98550f04c51a09500488fd5db1064437aac7",
+ "libgdk-pixbuf2.0-common_2.42.2+dfsg-1+deb11u1_all.deb": "e99738118ad4a63a4cfd7e34006fd379dd850b9527ec464a104b178a5038b5be",
+ "libgdk-pixbuf2.0-dev_2.40.2-2_amd64.deb": "98d1fe8b2fc224569a04d18f4a0efd6d5482feb47f85b1f4f3e149972a44a93e",
+ "libgirepository-1.0-1_1.66.1-1+b1_amd64.deb": "787e913bf56f19bc54720c3463ab8afe1cc9442536fde31e2a36afc3939f28c9",
+ "libgl-dev_1.3.2-1_amd64.deb": "a6487873f2706bbabf9346cdb190f47f23a1464f31cecf92c363bac37c342f2f",
+ "libgl1-mesa-dri_20.3.5-1_amd64.deb": "08e8bc20077e188da7061f77d23a336782d8463c0cc112fabbfa9c8b45923fd2",
+ "libgl1_1.3.2-1_amd64.deb": "f300f9610b5f05f1ce566c4095f1bf2170e512ac5d201c40d895b8fce29dec98",
+ "libglapi-mesa_20.3.5-1_amd64.deb": "aa8f8eaf13224cbb8729416be79350460f7f2230193b2da5d5e24f3dc7e9985f",
+ "libgles-dev_1.3.2-1_amd64.deb": "969e9197d8b8a36780f9b5d86f7c3066cdfef9dd7cdc3aee59a1870415c53578",
+ "libgles1_1.3.2-1_amd64.deb": "18425a2558be1de779c7c71ce780b133381f0db594a901839c6ae3d8e3f3c966",
+ "libgles2_1.3.2-1_amd64.deb": "367116f5e3b3a003a80203848b5ce1401451a67c2b2b9d6a383efc91badb0724",
+ "libglib2.0-0_2.66.8-1_amd64.deb": "995469490dcc8f667df8051a39dd5abd7149d849456c28af4e58cbfd6d6dc4f8",
+ "libglib2.0-bin_2.66.8-1_amd64.deb": "5adf4c916832ad4203fed68faacd4552361cbccc22f66f4504a7ad6fc955bddd",
+ "libglib2.0-data_2.66.8-1_all.deb": "be41a674336cefd00e2a468fe19c8bbf9f3fac86f39379e1b7acbad41f6af644",
+ "libglib2.0-dev_2.66.8-1_amd64.deb": "782fcfd549266048309b8da556377c16445bafe9f0aec31d9f246ac9b736d2aa",
+ "libglvnd-dev_1.3.2-1_amd64.deb": "e330ccbe6338789fd63212b55009dcce733265799395ad55b300cd1427234e7f",
+ "libglvnd0_1.3.2-1_amd64.deb": "52a4464d181949f5ed8f7e55cca67ba2739f019e93fcfa9d14e8d65efe98fffc",
+ "libglx-dev_1.3.2-1_amd64.deb": "5a50549948bc4363eab32b1083dad2165402c3628f2ee85e9a32563228cc61c1",
+ "libglx-mesa0_20.3.5-1_amd64.deb": "2d19e2addfbea965220e62f512318351f12bdfe7e180f265f00d0f2834a77833",
+ "libglx0_1.3.2-1_amd64.deb": "cb642200f7e28e6dbb4075110a0b441880eeec35c8a00a2198c59c53309e5e17",
+ "libgmp10_6.2.1+dfsg-1+deb11u1_amd64.deb": "fc117ccb084a98d25021f7e01e4dfedd414fa2118fdd1e27d2d801d7248aebbc",
+ "libgnutls30_3.7.1-5+deb11u2_amd64.deb": "ca2dbeb934a985f3ee1204f7a58001535ce49e8f8575d3fea08efbd4640773f1",
+ "libgpg-error0_1.38-2_amd64.deb": "16a507fb20cc58b5a524a0dc254a9cb1df02e1ce758a2d8abde0bc4a3c9b7c26",
+ "libgraphite2-3_1.3.14-1_amd64.deb": "31113b9e20c89d3b923da0540d6f30535b8d14f32e5904de89e34537fa87d59a",
+ "libgraphite2-dev_1.3.14-1_amd64.deb": "aa0437ff7c38b6e68a0bbcc3f18163677372e99fe3ec9673a552d8a8521aba64",
+ "libgtk-3-0_3.24.24-4+deb11u2_amd64.deb": "f58fcba87f2b7cb03a0f9f174817cc2ef18cd5dcfe41129b618ec3b7d5e0f8a0",
+ "libgtk-3-common_3.24.24-4+deb11u2_all.deb": "172d01f359af8f13cee93dba183e282ea5f059f2a418dfe66d35abf9dd60ddd7",
+ "libgtk-3-dev_3.24.24-4+deb11u2_amd64.deb": "24e7547f68a920c7d85ec38a102ba625e7f73d38ee2e7a494ba8b25eaf608062",
+ "libharfbuzz-dev_2.7.4-1_amd64.deb": "e2b5b9331990dc71da22fe05529cc72220e5449bcb98b08338c00e2f697cca65",
+ "libharfbuzz-gobject0_2.7.4-1_amd64.deb": "3c3cbf4150275173e7b4cdb0b12b8670867e70c27c0a31fd6559f4ce68a7dd84",
+ "libharfbuzz-icu0_2.7.4-1_amd64.deb": "43b41efde4c41c04b067f1d2917f33cb2d6a56b8e5d770e53ee71d7debdd241b",
+ "libharfbuzz0b_2.7.4-1_amd64.deb": "c76825341b5877240ff2511a376844a50ffda19d9d019ae65a5b3a97f9a1a183",
+ "libhogweed6_3.7.3-1_amd64.deb": "6aab2e892cdb2dfba45707601bc6c3b19aa228f70ae5841017f14c3b0ca3d22f",
+ "libice-dev_1.0.10-1_amd64.deb": "9d111d7e07104f7b9cc284d32e811ab01f376613f163b0580fbd7b61440ff669",
+ "libice6_1.0.10-1_amd64.deb": "452796e565c9d42386bd59990000ae9c37d85e142e00ee2b14df0787e2bbf970",
+ "libicu-dev_67.1-7_amd64.deb": "7932a6acfbfd76e1dbedcf171dafda9e549b8dc179a666043dbb3d5b733c4a29",
+ "libicu67_67.1-7_amd64.deb": "2bf5c46254f527865bfd6368e1120908755fa57d83634bd7d316c9b3cfd57303",
+ "libidn2-0_2.3.0-5_amd64.deb": "cb80cd769171537bafbb4a16c12ec427065795946b3415781bc9792e92d60b59",
+ "libjbig0_2.1-3.1+b2_amd64.deb": "9646d69eefce505407bf0437ea12fb7c2d47a3fd4434720ba46b642b6dcfd80f",
+ "libjpeg62-turbo_2.0.6-4_amd64.deb": "28de780a1605cf501c3a4ebf3e588f5110e814b208548748ab064100c32202ea",
+ "libjson-glib-1.0-0_1.6.2-1_amd64.deb": "c2db69dda6ceda43065d694c5ebd515900dd38d7231a74016f10a2d2a870f01d",
+ "libjson-glib-1.0-common_1.6.2-1_all.deb": "a938ec35a20dca2e5878a8750fb44683b67a5f7c2d23d383963803a9fcfac1a3",
+ "liblcms2-2_2.12~rc1-2_amd64.deb": "0608ecb6ed258814e390b52b3fb50f2a6d3239b5ecb1086292ae08be00a67b0f",
+ "libllvm11_11.0.1-2_amd64.deb": "eaff3c8dd6039af90b8b6bdbf33433e35d8c808a7aa195d0e3800ef5e61affff",
+ "liblz4-1_1.9.3-2_amd64.deb": "79ac6e9ca19c483f2e8effcc3401d723dd9dbb3a4ae324714de802adb21a8117",
+ "liblzo2-2_2.10-2_amd64.deb": "4f08e092c76e425295a498cd547dc9b8f6a595473f3020ab8c96309b29872636",
+ "libmd0_1.0.3-3_amd64.deb": "9e425b3c128b69126d95e61998e1b5ef74e862dd1fc953d91eebcc315aea62ea",
+ "libmount-dev_2.36.1-8+deb11u1_amd64.deb": "e2ab59f02398ff5f50d58ba5702a3dc27d47b6b028fccab03d0e8060e317f328",
+ "libmount1_2.36.1-8+deb11u1_amd64.deb": "a3d8673804f32e9716e33111714e250b6f1092770a52e21fab99d0ab4b48c5d9",
+ "libnettle8_3.7.3-1_amd64.deb": "e4f8ec31ed14518b241eb7b423ad5ed3f4a4e8ac50aae72c9fd475c569582764",
+ "libopengl-dev_1.3.2-1_amd64.deb": "7e598e73830ffb5d6fae58ebd1c769b6f7806dc92bd5649893b74f1302b47e82",
+ "libopengl0_1.3.2-1_amd64.deb": "4327a9f20b88e7bcb07af3b196121096877331b61eeed64467854eb0b525fc43",
+ "libp11-kit0_0.23.22-1_amd64.deb": "bfef5f31ee1c730e56e16bb62cc5ff8372185106c75bf1ed1756c96703019457",
+ "libpango-1.0-0_1.46.2-3_amd64.deb": "cfb3079a7397cc7d50eabe28ea70ce15ba371c84efafd8f8529ee047e667f523",
+ "libpango1.0-dev_1.46.2-3_amd64.deb": "5da5a8009ab6275b12193e277bf6d091b29203f058d246d5b8a184d1c7f0cde6",
+ "libpangocairo-1.0-0_1.46.2-3_amd64.deb": "f0489372e4bcb153d750934eb3cddd9104bc3a46d564aa10bef320ba89681d37",
+ "libpangoft2-1.0-0_1.46.2-3_amd64.deb": "78067d7222459902e22da6b4c1ab8ee84940752d25a5f3dea1a43f846a8562e3",
+ "libpangoxft-1.0-0_1.46.2-3_amd64.deb": "621545808843e84288039a55df16023ff872f48b3a155788dba7a1cea25c7a9b",
+ "libpciaccess0_0.16-1_amd64.deb": "f581ced157bd475477337860e7e7fcabeeb091444bc5a189c5c97adc8fcabda5",
+ "libpcre16-3_8.39-13_amd64.deb": "04ef146b0119a8a5ab1df09d990bd61a45bf99d2989aa248ebc7f72dbb99544e",
+ "libpcre2-16-0_10.36-2+deb11u1_amd64.deb": "386fc5684d0339469f0910aefc96f12d2b058dc22d096605f483a56475a37d39",
+ "libpcre2-32-0_10.36-2+deb11u1_amd64.deb": "b6d6b388adb390aae0690c0398b813222b0ed16a1705b8fb2acd1d190c03936a",
+ "libpcre2-8-0_10.36-2+deb11u1_amd64.deb": "ee192c8d22624eb9d0a2ae95056bad7fb371e5abc17e23e16b1de3ddb17a1064",
+ "libpcre2-dev_10.36-2+deb11u1_amd64.deb": "bd4bf9a13dc86c14b6ed8d822d5c8eb66b80418fe8a0484fe1cd6836a8381c49",
+ "libpcre2-posix2_10.36-2+deb11u1_amd64.deb": "f19dc0b4145836eb0c5ce462e16f546fb5298b9186d760d829cd0c171d0a2afd",
+ "libpcre3-dev_8.39-13_amd64.deb": "e588a2bd07e2770ad2fa9e3b02e359d3ff3c6f0c17a809365d3e97da7b0e64e0",
+ "libpcre32-3_8.39-13_amd64.deb": "961135f3ff2d00c2e46640b9730d9ddef80ae9d9037e2ec882ee8f6ce5dd48c9",
+ "libpcre3_8.39-13_amd64.deb": "48efcf2348967c211cd9408539edf7ec3fa9d800b33041f6511ccaecc1ffa9d0",
+ "libpcrecpp0v5_8.39-13_amd64.deb": "79e15b8d31f8561ad1c19f8c280d0a9fe280f7872701ef53c9bdfce6b3015a18",
+ "libpixman-1-0_0.40.0-1_amd64.deb": "55236a7d4b9db107eb480ac56b3aa786572ea577ba34323baf46aceb7ba6d012",
+ "libpixman-1-dev_0.40.0-1_amd64.deb": "bcde62aee0fe759798e8a4d3a3d9b0666ba5ab15d1cb9e69fa000ff23ba305cb",
+ "libproxy1v5_0.4.17-1_amd64.deb": "b21c1524b972dd72387ecb8b12c0a860738ce0832ed18fe7ffb9da6adc9b9e41",
+ "libpsl5_0.21.0-1.2_amd64.deb": "d716f5b4346ec85bb728f4530abeb1da4a79f696c72d7f774c59ba127c202fa7",
+ "libpthread-stubs0-dev_0.4-1_amd64.deb": "54632f160e1e8a43656a87195a547391038c4ca0f53291b849cd4457ba5dfde9",
+ "librest-0.7-0_0.8.1-1.1_amd64.deb": "5cd57a96145a362bf60428315ab3fc6c2f528ab38a06a905da2568575c23bdc8",
+ "libselinux1-dev_3.1-3_amd64.deb": "16b14d7e8ed88b9b07d1b52d84d04ab2fcdfcdc4b8cecc9dd34df06f3ce7d3fb",
+ "libselinux1_3.1-3_amd64.deb": "339f5ede10500c16dd7192d73169c31c4b27ab12130347275f23044ec8c7d897",
+ "libsensors-config_3.6.0-7_all.deb": "4265811140a591d27c99d026b63707d8235d98c73d7543c66ab9ec73c28523fc",
+ "libsensors5_3.6.0-7_amd64.deb": "b9cb9a081ea3c9b68ef047d7e51f3b84bccde1a2467d5657df4c5d54775b187e",
+ "libsepol1-dev_3.1-1_amd64.deb": "1bec586de489db87c8746a6eeed27982915fc578c91e9e78ef39773ab824e023",
+ "libsepol1_3.1-1_amd64.deb": "b6057dc6806a6dfaef74b09d84d1f18716d7a6d2f1da30520cef555210c6af62",
+ "libsm-dev_1.2.3-1_amd64.deb": "2ff8641d3217dc1a0f26514f5d8de2009669423a4aa0db46b3df564a8b367026",
+ "libsm6_1.2.3-1_amd64.deb": "22a420890489023346f30fecef14ea900a0788e7bf959ef826aabb83944fccfb",
+ "libsoup-gnome2.4-1_2.72.0-2_amd64.deb": "7fdc774b567e3a5e0881aa01fcfcac637fdeeb8ea6233b710571e1f5b3a994b6",
+ "libsoup2.4-1_2.72.0-2_amd64.deb": "32dad5305be0faa619df36688a20d187ba915f02e9e184cc5c3c6e3d98259e9c",
+ "libsqlite3-0_3.34.1-3_amd64.deb": "a0b8d3acf4a0483048637637d269be93af48d5c16f6f139f53edd13384ad4686",
+ "libsystemd0_247.3-7+deb11u1_amd64.deb": "0bce44fd32e9fa18b68cb89f4010939b9984b9782db2d1985b041fc96e9a02b8",
+ "libtasn1-6_4.16.0-2_amd64.deb": "fd7a200100298c2556e67bdc1a5faf5cf21c3136fa47f381d7e9769233ee88a1",
+ "libthai-data_0.1.28-3_all.deb": "64750cb822e54627a25b5a00cde06e233b5dea28571690215f672af97937f01b",
+ "libthai-dev_0.1.28-3_amd64.deb": "b633b5fbe6220f69fe78019817a3176124e64c5e402cf1142bac14ec93bfbb4b",
+ "libthai0_0.1.28-3_amd64.deb": "446e2b6e8e8a0f5f6c0de0a40c2aa4e1c2cf806efc450c37f5358c7ff1092d6a",
+ "libtiff5_4.2.0-1+deb11u1_amd64.deb": "b22d25e14421a36c4c3b721c04c6312d79ccd91c9a0e2291f58e36b8d4a07fbb",
+ "libtinfo6_6.2+20201114-2_amd64.deb": "aeaf942c71ecc0ed081efdead1a1de304dcd513a9fc06791f26992e76986597b",
+ "libudev1_247.3-7+deb11u1_amd64.deb": "6c654be062de0fd8696808cbc0bfd5ff81e7163c14f0136a132090eda2363831",
+ "libunistring2_0.9.10-4_amd64.deb": "654433ad02d3a8b05c1683c6c29a224500bf343039c34dcec4e5e9515345e3d4",
+ "libuuid1_2.36.1-8+deb11u1_amd64.deb": "31250af4dd3b7d1519326a9a6764d1466a93d8f498cf6545058761ebc38b2823",
+ "libvulkan1_1.2.162.0-1_amd64.deb": "8b3a6e5db7d8bdc369a0d276bfae1551ffc0fa31dbd193d56655c8f553868361",
+ "libwayland-bin_1.18.0-2~exp1.1_amd64.deb": "774e97053d524549044b332469d13eec70c989b4bc00a592019512c17a92978e",
+ "libwayland-client0_1.18.0-2~exp1.1_amd64.deb": "4baf16bb3a35823251453368ee078b6be6a14f97b05c19783b5acd4232a608ea",
+ "libwayland-cursor0_1.18.0-2~exp1.1_amd64.deb": "1b48d1d8e17a95b28a2876c7f2a95667ee1618a5f586d4dff05aeb09488172cb",
+ "libwayland-dev_1.18.0-2~exp1.1_amd64.deb": "3265bf05c0cea760d0e8f5fb5fc68b0f154911de23503e02232dfa59f6b6490c",
+ "libwayland-egl1_1.18.0-2~exp1.1_amd64.deb": "b98e636f08eca9e818e326fc8cd75810dbb50b1ed4e3586c2394e11248e29275",
+ "libwayland-server0_1.18.0-2~exp1.1_amd64.deb": "1df9a6e304bdaebdd53e1044c6eadcda95c914119e9426c2866eaa619a49c85b",
+ "libwebp6_0.6.1-2.1_amd64.deb": "52bfd0f8d3a1bbd2c25fcd72fab857d0f24aea35874af68e057dde869ae3902c",
+ "libx11-6_1.7.2-1_amd64.deb": "086bd667fc07369472a923da015d182bb0c15a72228a5c0e6ddbcbeaab70acd2",
+ "libx11-data_1.7.2-1_all.deb": "049b7eabced516acfdf44a5e81c26d108b16e4987e5d7604ea53eaade74027fb",
+ "libx11-dev_1.7.2-1_amd64.deb": "11e5f9dcded1a1226b3ee02847b86edce525240367b3989274a891a43dc49f5f",
+ "libx11-xcb1_1.7.2-1_amd64.deb": "1f9f2dbe7744a2bb7f855d819f43167df095fe7d5291546bec12865aed045e0c",
+ "libxau-dev_1.0.9-1_amd64.deb": "d1a7f5d484e0879b3b2e8d512894744505e53d078712ce65903fef2ecfd824bb",
+ "libxau6_1.0.9-1_amd64.deb": "679db1c4579ec7c61079adeaae8528adeb2e4bf5465baa6c56233b995d714750",
+ "libxcb-dri2-0_1.14-3_amd64.deb": "fbfc7d55fa00ab7068d015c185363370215c857ac9484d7020c2d9c38c8401b2",
+ "libxcb-dri3-0_1.14-3_amd64.deb": "4dd503b321253f210fe546aae8fe5061fc7d30015cf5580d7843432a71ebc772",
+ "libxcb-glx0_1.14-3_amd64.deb": "61ae35a71148038aad04b021b3adfa0dee4fc06d98e045ec9edfd9e850324876",
+ "libxcb-present0_1.14-3_amd64.deb": "7937af87426de2ed382ba0d6204fee58f4028b332625e2727ebb7ca9a1b32028",
+ "libxcb-render0-dev_1.14-3_amd64.deb": "f3335e206e938c760df5f933e35f370e850050e5c2c9ce0568f190970a6cac41",
+ "libxcb-render0_1.14-3_amd64.deb": "3d653df34e5cd35a78a9aff1d90c18ec0200e5574e27bc779315b855bea2ecc0",
+ "libxcb-shm0-dev_1.14-3_amd64.deb": "283d20ecde030b6905e7042f427a434a6334556a6475b11422278919f4c0c840",
+ "libxcb-shm0_1.14-3_amd64.deb": "0751b48b1c637b5b0cb080159c29b8dd83af8ec771a21c8cc26d180aaab0d351",
+ "libxcb-sync1_1.14-3_amd64.deb": "53e7f18c8a95b2be2024537a753b6bd914af5f4c7aeed175f61155a5a3c8fe88",
+ "libxcb-xfixes0_1.14-3_amd64.deb": "939b29a4eaad5972ba379c2b5f29cf51d7d947b10e68cc2fe96238efcd3d63c2",
+ "libxcb1-dev_1.14-3_amd64.deb": "b75544f334c8963b8b7b0e8a88f8a7cde95a714dddbcda076d4beb669a961b58",
+ "libxcb1_1.14-3_amd64.deb": "d5e0f047ed766f45eb7473947b70f9e8fddbe45ef22ecfd92ab712c0671a93ac",
+ "libxcomposite-dev_0.4.5-1_amd64.deb": "6aecea058e55f46341be898d6e21b933fee5a314e3133ec33b4b88441e7d52b4",
+ "libxcomposite1_0.4.5-1_amd64.deb": "4c26ebf519d2ebc22fc1416dee45e12c4c4ef68aa9b2ed890356830df42b652a",
+ "libxcursor-dev_1.2.0-2_amd64.deb": "c84c43ad4d596bd673288f6035ced4755468b873149181936a0c1fc99cff78aa",
+ "libxcursor1_1.2.0-2_amd64.deb": "d9fee761e4c50572c3ce3c3965b70fcfecd277d0d7d598e102134d12757a3d11",
+ "libxdamage-dev_1.1.5-2_amd64.deb": "34a580b62466411b34a0be2bb0d00c3ec268da96e80d0adc40b379c97e9bac37",
+ "libxdamage1_1.1.5-2_amd64.deb": "1acf6d6117929a7df346d355caeb579798d75feb7e3b3aae58a2d1af735b444f",
+ "libxdmcp-dev_1.1.2-3_amd64.deb": "c6733e5f6463afd261998e408be6eb37f24ce0a64b63bed50a87ddb18ebc1699",
+ "libxdmcp6_1.1.2-3_amd64.deb": "ecb8536f5fb34543b55bb9dc5f5b14c9dbb4150a7bddb3f2287b7cab6e9d25ef",
+ "libxext-dev_1.3.3-1.1_amd64.deb": "0aa17565287ca8a37914e043789ee33c6e1f987acf346dac7175165009c5db7c",
+ "libxext6_1.3.3-1.1_amd64.deb": "dc1ff8a2b60c7dd3c8917ffb9aa65ee6cda52648d9150608683c47319d1c0c8c",
+ "libxfixes-dev_5.0.3-2_amd64.deb": "bacfcd67ca931839a2e2ae87922ecb40e2870470afa86d0c7245288825da2340",
+ "libxfixes3_5.0.3-2_amd64.deb": "58622d0d65c3535bd724c4da62ae7acb71e0e8f527bcbd65daf8c97e6f0ef843",
+ "libxft-dev_2.3.2-2_amd64.deb": "c6919b13423d4e3b41b7650b2d9bb6f6deac2906b3d51d047b5898a169ebc13c",
+ "libxft2_2.3.2-2_amd64.deb": "cd71384b4d511cba69bcee29af326943c7ca12450765f44c40d246608c779aad",
+ "libxi-dev_1.7.10-1_amd64.deb": "736309ff476f0e1594f855cf44e2fb20bf1e594518ce2259eb9b2dd93917f2db",
+ "libxi6_1.7.10-1_amd64.deb": "4d583f43b5396ca5434100a7274613e9983357d80875a47b29a4f3218fe0bec0",
+ "libxinerama-dev_1.1.4-2_amd64.deb": "18047f52c3a1294d61bc2642d22d05bd879c15393c4e8b4ac2ee6a5061585b9b",
+ "libxinerama1_1.1.4-2_amd64.deb": "f692c854935571ee44fe313541d8a9f678a4f11dc513bc43b9d0a501c6dff0bd",
+ "libxkbcommon-dev_1.0.3-2_amd64.deb": "1202d8c64e670876b58f5b3d3f797b1848ec462f7caeef8b1e597ecea18570b6",
+ "libxkbcommon0_1.0.3-2_amd64.deb": "d74d0b9f0a6641b44c279644c7ac627fa7a9b92350b7c6ff37da94352885bcfc",
+ "libxml2_2.9.10+dfsg-6.7+deb11u2_amd64.deb": "4e0fe50fee6c42eeb8a77c55f08baca4f7ebc7d443760ffaaf5f437274f25800",
+ "libxrandr-dev_1.5.1-1_amd64.deb": "67d40174015e8b4e3e2fe3b6fb2990943321a168e0fbb2d12082f637914a0a2e",
+ "libxrandr2_1.5.1-1_amd64.deb": "8fdd8ba4a8ad819731d6bbd903b52851a2ec2f9ef4139d880e9be421ea61338c",
+ "libxrender-dev_0.9.10-1_amd64.deb": "135ed7c8a589e17d21718a91b5a7da48159f33c85e0b337aae9b9f484d3a4954",
+ "libxrender1_0.9.10-1_amd64.deb": "3ea17d07b5aa89012130e2acd92f0fc0ea67314e2f5eab6e33930ef688f48294",
+ "libxshmfence1_1.3-1_amd64.deb": "1a38142e40e3d32dc4f9a326bf5617363b7d9b4bb762fdcdd262f2192092024d",
+ "libxtst-dev_1.2.3-1_amd64.deb": "9ed56e0fd5807afe20cfee8fad16c657c6d7410d7934d8726584794bd77ea989",
+ "libxtst6_1.2.3-1_amd64.deb": "7072f9be17abdb9c5af7d052b19c84d1a6c1c13c30c120a98d284ba73d2da73f",
+ "libxxf86vm1_1.1.4-1+b2_amd64.deb": "6f4ca916aaec26d7000fa7f58de3f71119309ab7590ce1f517abfe1825a676c7",
+ "libz3-4_4.8.10-1_amd64.deb": "7a38c2dd985eb9315857588ee06ff297e2b16de159dec85bd2777a43ebe9f458",
+ "libzstd1_1.4.8+dfsg-2.1_amd64.deb": "5dcadfbb743bfa1c1c773bff91c018f835e8e8c821d423d3836f3ab84773507b",
+ "pango1.0-tools_1.46.2-3_amd64.deb": "e622e68a9451c9d15fd2a5c4c4a95884f33bdfece63f9c0d6cd3953c5d202e74",
+ "perl_5.32.1-4+deb11u2_amd64.deb": "1cebc4516ed7c240b812c7bdd7e6ea0810f513152717ca17ce139ee0dfbc7b0d",
+ "pkg-config_0.29.2-1_amd64.deb": "09a05a23c5fd5baacd488255a6b0114909210691b830fb951acd276e9bcd632a",
+ "sensible-utils_0.0.14_all.deb": "b9a447dc4ec8714196b037e20a2209e62cd669f5450222952f259bda4416b71f",
+ "shared-mime-info_2.0-1_amd64.deb": "de0a814e186af5a941e1fcd3044da62eb155638fcf9616d6005bcfc6696bbe67",
+ "ttf-bitstream-vera_1.10-8.1_all.deb": "ba622edf73744b2951bbd20bfc113a1a875a9b0c6fed1ac9e9c7f4b54dd8a048",
+ "ucf_3.0043_all.deb": "ebef6bcd777b5c0cc2699926f2159db08433aed07c50cb321fd828b28c5e8d53",
+ "uuid-dev_2.36.1-8+deb11u1_amd64.deb": "90a533bbb3b82f5c9bedc5da28965ca8223913099f8ac67213e4f8828bfdd2a1",
+ "wayland-protocols_1.20-1_all.deb": "09bcb6b1d7735a0190ec85f73680dfd53cfa91ff139c8b3d4e18377f94bb6599",
+ "x11-common_7.7+22_all.deb": "5d1c3287826f60c3a82158b803b9c0489b8aad845ca23a53a982eba3dbb82aa3",
+ "x11proto-core-dev_2020.1-1_all.deb": "92941b1b2a7889a67e952a9301339202b6b390b77af939a26ee15c94ef4fad7e",
+ "x11proto-dev_2020.1-1_all.deb": "d5568d587d9ad2664c34c14b0ac538ccb3c567e126ee5291085a8de704a565f5",
+ "x11proto-input-dev_2020.1-1_all.deb": "f2b5dbb98ddafb56b3a6d4d5545812b98e272c146f79adb41e49533eeaa97d3f",
+ "x11proto-randr-dev_2020.1-1_all.deb": "ca63b15ebe65d1e45868d72eb87cd447be3adeb5cc25787db09f65ef05e30c66",
+ "x11proto-record-dev_2020.1-1_all.deb": "dc0ceb54206b03107d31d0ce8665d47ce7c7debac5c3e072e041cdc37f176d3f",
+ "x11proto-render-dev_2020.1-1_all.deb": "f622a9bdd90d51305cd92ee3c5d30ca82f1a20aea3632514965afed6e85589c7",
+ "x11proto-xext-dev_2020.1-1_all.deb": "61e858b8758b8ff63dfc8206d3b00bfbe3ad36ef133dea41b3c5b73dc427d41b",
+ "x11proto-xinerama-dev_2020.1-1_all.deb": "0183efc631edb1308b2bc38ae08f3dc27db735f8d1e84d87bde6416fa023c70d",
+ "xkb-data_2.29-2_all.deb": "9122cccc67e6b3c3aef2fa9c50ef9d793a12f951c76698a02b1f4ceb9e3634e5",
+ "xorg-sgml-doctools_1.11-1.1_all.deb": "168345058319094e475a87ace66f5fb6ae802109650ea8434d672117982b5d0a",
+ "xtrans-dev_1.4.0-1_all.deb": "9ce1af9464faee0c679348dd11cdf63934c12e734a64e0903692b0cb5af38e06",
+ "zlib1g_1.2.11.dfsg-2+deb11u2_amd64.deb": "03d2ab2174af76df6f517b854b77460fbdafc3dac0dca979317da67538159a3e",
+}
diff --git a/debian/packages.bzl b/debian/packages.bzl
index 0f2784a..dfb4ed8 100644
--- a/debian/packages.bzl
+++ b/debian/packages.bzl
@@ -24,7 +24,7 @@
# 6. Add a new "new_http_archive" entry to the WORKSPACE file for the tarball
# you just uploaded.
-def download_packages(name, packages, excludes = [], force_includes = [], target_compatible_with = None):
+def download_packages(name, packages, excludes = [], force_includes = [], force_excludes = [], target_compatible_with = None):
"""Downloads a set of packages as well as their dependencies.
You can also specify excludes in case some of the dependencies are meta
@@ -34,10 +34,17 @@
list to use in a .bzl file. Once you have the packages on
https://www.frc971.org/Build-Dependencies/ you can add them to a to
combine_packages rule.
+
+ force_includes lets you include packages that are excluded by default. The
+ dependencies of these force-included packages are also force-included. To
+ counter-act that, you can use "force_excludes". The force-excluded packages
+ are excluded even if they're pulled in as a dependency from a
+ "force_includes" package.
"""
package_list = " ".join(packages)
excludes_list = " ".join(["--exclude=%s" % e for e in excludes])
force_includes = " ".join(["--force-include=%s" % i for i in force_includes])
+ force_excludes = " ".join(["--force-exclude=%s" % e for e in force_excludes])
native.genrule(
name = name + "_gen",
outs = ["%s.sh" % name],
@@ -58,8 +65,8 @@
# --- end runfiles.bash initialization v2 ---
-exec "$$(rlocation org_frc971/debian/download_packages)" %s %s %s "$$@"
-END""" % (force_includes, excludes_list, package_list),
+exec "$$(rlocation org_frc971/debian/download_packages)" %s %s %s %s "$$@"
+END""" % (force_includes, force_excludes, excludes_list, package_list),
target_compatible_with = target_compatible_with,
)
native.sh_binary(
diff --git a/frc971/control_loops/python/path_edit.py b/frc971/control_loops/python/path_edit.py
index 7c742c8..437b242 100755
--- a/frc971/control_loops/python/path_edit.py
+++ b/frc971/control_loops/python/path_edit.py
@@ -48,7 +48,10 @@
# init editing / viewing modes and pointer location
self.mode = Mode.kPlacing
self.mousex = 0
+ self.lastx = 0
self.mousey = 0
+ self.lasty = 0
+ self.drag_start = None
self.module_path = os.path.dirname(os.path.realpath(sys.argv[0]))
self.path_to_export = os.path.join(self.module_path,
'points_for_pathedit.json')
@@ -406,6 +409,8 @@
self.queue_draw()
def do_button_release_event(self, event):
+ self.drag_start = None
+
self.attempt_append_multisplines()
self.mousex, self.mousey = self.input_transform.transform_point(
event.x, event.y)
@@ -430,6 +435,9 @@
self.mousex, self.mousey = self.input_transform.transform_point(
event.x, event.y)
+ self.lastx = event.x
+ self.lasty = event.y
+
if self.mode == Mode.kPlacing:
if self.active_multispline.addPoint(self.mousex, self.mousey):
self.mode = Mode.kEditing
@@ -462,6 +470,9 @@
index_multisplines, index_splines,
index_points)
+ if self.control_point_index == None:
+ self.drag_start = (event.x, event.y)
+
multispline, result = Multispline.nearest_distance(
self.multisplines, cur_p)
if result and result.fun < 0.1:
@@ -484,6 +495,14 @@
multispline.update_lib_spline()
self.graph.schedule_recalculate(self.multisplines)
+
+ if self.mode == Mode.kEditing and self.drag_start != None and self.control_point_index == None:
+
+ self.zoom_transform.translate(event.x - self.lastx,
+ event.y - self.lasty)
+ self.lastx = event.x
+ self.lasty = event.y
+
self.queue_draw()
def do_scroll_event(self, event):
@@ -505,9 +524,9 @@
scale = (self.field.width + scale_by) / self.field.width
# This restricts the amount it can be scaled.
- if self.zoom_transform.xx <= 0.5:
+ if self.zoom_transform.xx <= 0.05:
scale = max(scale, 1)
- elif self.zoom_transform.xx >= 16:
+ elif self.zoom_transform.xx >= 32:
scale = min(scale, 1)
# undo the scaled translation that the old zoom transform did
diff --git a/frc971/vision/BUILD b/frc971/vision/BUILD
index b2b815a..f5bb94d 100644
--- a/frc971/vision/BUILD
+++ b/frc971/vision/BUILD
@@ -79,6 +79,7 @@
"//aos/events/logging:log_reader",
"//frc971/analysis:in_process_plotter",
"//frc971/control_loops/drivetrain:improved_down_estimator",
+ "//frc971/vision:visualize_robot",
"//frc971/wpilib:imu_batch_fbs",
"//frc971/wpilib:imu_fbs",
"//third_party:opencv",
@@ -88,6 +89,46 @@
],
)
+flatbuffer_cc_library(
+ name = "target_map_fbs",
+ srcs = ["target_map.fbs"],
+ gen_reflections = 1,
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+)
+
+cc_library(
+ name = "target_mapper",
+ srcs = ["target_mapper.cc"],
+ hdrs = ["target_mapper.h"],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":geometry_lib",
+ ":target_map_fbs",
+ "//aos/events:simulated_event_loop",
+ "//frc971/control_loops:control_loop",
+ "//frc971/vision/ceres:pose_graph_2d_lib",
+ "//third_party:opencv",
+ "@com_google_ceres_solver//:ceres",
+ "@org_tuxfamily_eigen//:eigen",
+ ],
+)
+
+cc_test(
+ name = "target_mapper_test",
+ srcs = [
+ "target_mapper_test.cc",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ deps = [
+ ":target_mapper",
+ "//aos/events:simulated_event_loop",
+ "//aos/testing:googletest",
+ "//aos/testing:random_seed",
+ ],
+)
+
cc_library(
name = "geometry_lib",
hdrs = [
@@ -112,3 +153,36 @@
"//aos/testing:googletest",
],
)
+
+cc_library(
+ name = "visualize_robot",
+ srcs = [
+ "visualize_robot.cc",
+ ],
+ hdrs = [
+ "visualize_robot.h",
+ ],
+ deps = [
+ "//aos:init",
+ "//third_party:opencv",
+ "@com_google_absl//absl/strings:str_format",
+ "@org_tuxfamily_eigen//:eigen",
+ ],
+)
+
+cc_binary(
+ name = "visualize_robot_sample",
+ srcs = [
+ "visualize_robot_sample.cc",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//aos:init",
+ "//frc971/vision:visualize_robot",
+ "//third_party:opencv",
+ "@com_github_google_glog//:glog",
+ "@com_google_ceres_solver//:ceres",
+ "@org_tuxfamily_eigen//:eigen",
+ ],
+)
diff --git a/frc971/vision/calibration_accumulator.cc b/frc971/vision/calibration_accumulator.cc
index ac1946c..f1ab4fc 100644
--- a/frc971/vision/calibration_accumulator.cc
+++ b/frc971/vision/calibration_accumulator.cc
@@ -1,20 +1,22 @@
#include "frc971/vision/calibration_accumulator.h"
-#include <opencv2/aruco/charuco.hpp>
-#include <opencv2/calib3d.hpp>
-#include <opencv2/features2d.hpp>
+#include <algorithm>
+#include <limits>
#include <opencv2/highgui/highgui.hpp>
-#include <opencv2/imgproc.hpp>
#include "Eigen/Dense"
#include "aos/events/simulated_event_loop.h"
+#include "aos/network/team_number.h"
#include "aos/time/time.h"
#include "frc971/control_loops/quaternion_utils.h"
-#include "frc971/wpilib/imu_batch_generated.h"
#include "frc971/vision/charuco_lib.h"
+#include "frc971/wpilib/imu_batch_generated.h"
DEFINE_bool(display_undistorted, false,
"If true, display the undistorted image.");
+DEFINE_string(save_path, "", "Where to store annotated images");
+DEFINE_bool(save_valid_only, false,
+ "If true, only save images with valid pose estimates");
namespace frc971 {
namespace vision {
@@ -27,59 +29,83 @@
void CalibrationData::AddCameraPose(
distributed_clock::time_point distributed_now, Eigen::Vector3d rvec,
Eigen::Vector3d tvec) {
- // Always start with IMU reading...
- if (!imu_points_.empty() && imu_points_[0].first < distributed_now) {
+ // Always start with IMU (or turret) reading...
+ // Note, we may not have a turret, so need to handle that case
+ // If we later get a turret point, then we handle removal of camera points in
+ // AddTurret
+ if ((!imu_points_.empty() && imu_points_[0].first < distributed_now) &&
+ (turret_points_.empty() || turret_points_[0].first < distributed_now)) {
rot_trans_points_.emplace_back(distributed_now, std::make_pair(rvec, tvec));
}
}
void CalibrationData::AddImu(distributed_clock::time_point distributed_now,
Eigen::Vector3d gyro, Eigen::Vector3d accel) {
- imu_points_.emplace_back(distributed_now, std::make_pair(gyro, accel));
+ double zero_threshold = 1e-12;
+ // We seem to be getting 0 readings on IMU, so ignore for now
+ // TODO<Jim>: I think this has been resolved in HandleIMU, but want to leave
+ // this here just in case there are other ways this could happen
+ if ((fabs(accel(0)) < zero_threshold) && (fabs(accel(1)) < zero_threshold) &&
+ (fabs(accel(2)) < zero_threshold)) {
+ LOG(FATAL) << "Ignoring zero value from IMU accelerometer: " << accel
+ << " (gyro is " << gyro << ")";
+ } else {
+ imu_points_.emplace_back(distributed_now, std::make_pair(gyro, accel));
+ }
}
void CalibrationData::AddTurret(
aos::distributed_clock::time_point distributed_now, Eigen::Vector2d state) {
- // We want the turret to be known too when solving. But, we don't know if we
- // are going to have a turret until we get the first reading. In that case,
- // blow away any camera readings from before.
- while (!rot_trans_points_.empty() &&
- rot_trans_points_[0].first < distributed_now) {
- rot_trans_points_.erase(rot_trans_points_.begin());
+ // We want the turret to be known too when solving. But, we don't know if
+ // we are going to have a turret until we get the first reading. In that
+ // case, blow away any camera readings from before.
+ // NOTE: Since the IMU motion is independent of the turret position, we don't
+ // need to remove the IMU readings before the turret
+ if (turret_points_.empty()) {
+ while (!rot_trans_points_.empty() &&
+ rot_trans_points_[0].first < distributed_now) {
+ LOG(INFO) << "Erasing, distributed " << distributed_now;
+ rot_trans_points_.erase(rot_trans_points_.begin());
+ }
}
turret_points_.emplace_back(distributed_now, state);
}
void CalibrationData::ReviewData(CalibrationDataObserver *observer) const {
- size_t next_imu_point = 0;
size_t next_camera_point = 0;
- while (true) {
- if (next_imu_point != imu_points_.size()) {
- // There aren't that many combinations, so just brute force them all
- // rather than being too clever.
- if (next_camera_point != rot_trans_points_.size()) {
- if (imu_points_[next_imu_point].first >
- rot_trans_points_[next_camera_point].first) {
- // Camera!
- observer->UpdateCamera(rot_trans_points_[next_camera_point].first,
- rot_trans_points_[next_camera_point].second);
- ++next_camera_point;
- } else {
- // IMU!
- observer->UpdateIMU(imu_points_[next_imu_point].first,
- imu_points_[next_imu_point].second);
- ++next_imu_point;
- }
+ size_t next_imu_point = 0;
+ size_t next_turret_point = 0;
+
+ // Just go until one of the data streams runs out. We lose a few points, but
+ // it makes the logic much easier
+ while (
+ next_camera_point != rot_trans_points_.size() &&
+ next_imu_point != imu_points_.size() &&
+ (turret_points_.empty() || next_turret_point != turret_points_.size())) {
+ // If camera_point is next, update it
+ if ((rot_trans_points_[next_camera_point].first <=
+ imu_points_[next_imu_point].first) &&
+ (turret_points_.empty() ||
+ (rot_trans_points_[next_camera_point].first <=
+ turret_points_[next_turret_point].first))) {
+ // Camera!
+ observer->UpdateCamera(rot_trans_points_[next_camera_point].first,
+ rot_trans_points_[next_camera_point].second);
+ ++next_camera_point;
+ } else {
+ // If it's not the camera, check if IMU is next
+ if (turret_points_.empty() || (imu_points_[next_imu_point].first <=
+ turret_points_[next_turret_point].first)) {
+ // IMU!
+ observer->UpdateIMU(imu_points_[next_imu_point].first,
+ imu_points_[next_imu_point].second);
+ ++next_imu_point;
} else {
- if (next_camera_point != rot_trans_points_.size()) {
- // Camera!
- observer->UpdateCamera(rot_trans_points_[next_camera_point].first,
- rot_trans_points_[next_camera_point].second);
- ++next_camera_point;
- } else {
- // Nothing left for either list of points, so we are done.
- break;
- }
+ // If it's not IMU or camera, and turret_points is not empty, it must be
+ // the turret!
+ observer->UpdateTurret(turret_points_[next_turret_point].first,
+ turret_points_[next_turret_point].second);
+ ++next_turret_point;
}
}
}
@@ -98,17 +124,42 @@
charuco_extractor_(
image_event_loop_, pi,
[this](cv::Mat rgb_image, monotonic_clock::time_point eof,
- std::vector<int> charuco_ids,
- std::vector<cv::Point2f> charuco_corners, bool valid,
- Eigen::Vector3d rvec_eigen, Eigen::Vector3d tvec_eigen) {
+ std::vector<cv::Vec4i> charuco_ids,
+ std::vector<std::vector<cv::Point2f>> charuco_corners,
+ bool valid, std::vector<Eigen::Vector3d> rvecs_eigen,
+ std::vector<Eigen::Vector3d> tvecs_eigen) {
HandleCharuco(rgb_image, eof, charuco_ids, charuco_corners, valid,
- rvec_eigen, tvec_eigen);
+ rvecs_eigen, tvecs_eigen);
+ }),
+ image_callback_(
+ image_event_loop_,
+ absl::StrCat("/pi",
+ std::to_string(aos::network::ParsePiNumber(pi).value()),
+ "/camera"),
+ [this](cv::Mat rgb_image, const monotonic_clock::time_point eof) {
+ charuco_extractor_.HandleImage(rgb_image, eof);
}),
data_(data) {
imu_factory_->OnShutdown([]() { cv::destroyAllWindows(); });
+ // Check for IMUValuesBatch topic on both /localizer and /drivetrain channels,
+ // since both are valid/possible
+ std::string imu_channel;
+ if (imu_event_loop->HasChannel<frc971::IMUValuesBatch>("/localizer")) {
+ imu_channel = "/localizer";
+ } else if (imu_event_loop->HasChannel<frc971::IMUValuesBatch>(
+ "/drivetrain")) {
+ imu_channel = "/drivetrain";
+ } else {
+ LOG(FATAL) << "Couldn't find channel with IMU data for either localizer or "
+ "drivtrain";
+ }
+
+ VLOG(2) << "Listening for " << frc971::IMUValuesBatch::GetFullyQualifiedName()
+ << " on channel: " << imu_channel;
+
imu_event_loop_->MakeWatcher(
- "/drivetrain", [this](const frc971::IMUValuesBatch &imu) {
+ imu_channel, [this](const frc971::IMUValuesBatch &imu) {
if (!imu.has_readings()) {
return;
}
@@ -118,28 +169,17 @@
});
}
-void Calibration::HandleCharuco(cv::Mat rgb_image,
- const monotonic_clock::time_point eof,
- std::vector<int> /*charuco_ids*/,
- std::vector<cv::Point2f> /*charuco_corners*/,
- bool valid, Eigen::Vector3d rvec_eigen,
- Eigen::Vector3d tvec_eigen) {
+void Calibration::HandleCharuco(
+ cv::Mat rgb_image, const monotonic_clock::time_point eof,
+ std::vector<cv::Vec4i> /*charuco_ids*/,
+ std::vector<std::vector<cv::Point2f>> /*charuco_corners*/, bool valid,
+ std::vector<Eigen::Vector3d> rvecs_eigen,
+ std::vector<Eigen::Vector3d> tvecs_eigen) {
if (valid) {
- data_->AddCameraPose(image_factory_->ToDistributedClock(eof), rvec_eigen,
- tvec_eigen);
-
- // Z -> up
- // Y -> away from cameras 2 and 3
- // X -> left
- Eigen::Vector3d imu(last_value_.accelerometer_x,
- last_value_.accelerometer_y,
- last_value_.accelerometer_z);
-
- Eigen::Quaternion<double> imu_to_camera(
- Eigen::AngleAxisd(-0.5 * M_PI, Eigen::Vector3d::UnitX()));
-
- Eigen::Quaternion<double> board_to_world(
- Eigen::AngleAxisd(0.5 * M_PI, Eigen::Vector3d::UnitX()));
+ CHECK(rvecs_eigen.size() > 0) << "Require at least one target detected";
+ // We only use one (the first) target detected for calibration
+ data_->AddCameraPose(image_factory_->ToDistributedClock(eof),
+ rvecs_eigen[0], tvecs_eigen[0]);
Eigen::IOFormat HeavyFmt(Eigen::FullPrecision, 0, ", ", ",\n", "[", "]",
"[", "]");
@@ -148,26 +188,47 @@
std::chrono::duration_cast<std::chrono::duration<double>>(
image_event_loop_->monotonic_now() - eof)
.count();
- LOG(INFO) << std::fixed << std::setprecision(6) << "Age: " << age_double
- << ", Pose is R:" << rvec_eigen.transpose().format(HeavyFmt)
- << " T:" << tvec_eigen.transpose().format(HeavyFmt);
+ VLOG(1) << std::fixed << std::setprecision(6) << "Age: " << age_double
+ << ", Pose is R:" << rvecs_eigen[0].transpose().format(HeavyFmt)
+ << "\nT:" << tvecs_eigen[0].transpose().format(HeavyFmt);
}
- cv::imshow("Display", rgb_image);
+ if (FLAGS_visualize) {
+ if (FLAGS_display_undistorted) {
+ const cv::Size image_size(rgb_image.cols, rgb_image.rows);
+ cv::Mat undistorted_rgb_image(image_size, CV_8UC3);
+ cv::undistort(rgb_image, undistorted_rgb_image,
+ charuco_extractor_.camera_matrix(),
+ charuco_extractor_.dist_coeffs());
- if (FLAGS_display_undistorted) {
- const cv::Size image_size(rgb_image.cols, rgb_image.rows);
- cv::Mat undistorted_rgb_image(image_size, CV_8UC3);
- cv::undistort(rgb_image, undistorted_rgb_image,
- charuco_extractor_.camera_matrix(),
- charuco_extractor_.dist_coeffs());
+ cv::imshow("Display undist", undistorted_rgb_image);
+ }
- cv::imshow("Display undist", undistorted_rgb_image);
+ cv::imshow("Display", rgb_image);
+ cv::waitKey(1);
+ }
+
+ if (FLAGS_save_path != "") {
+ if (!FLAGS_save_valid_only || valid) {
+ static int img_count = 0;
+ std::string image_name = absl::StrFormat("/img_%06d.png", img_count);
+ std::string path = FLAGS_save_path + image_name;
+ VLOG(2) << "Saving image to " << path;
+ cv::imwrite(path, rgb_image);
+ img_count++;
+ }
}
}
void Calibration::HandleIMU(const frc971::IMUValues *imu) {
- VLOG(1) << "IMU " << imu;
+ // Need to check for valid values, since we sometimes don't get them
+ if (!imu->has_gyro_x() || !imu->has_gyro_y() || !imu->has_gyro_z() ||
+ !imu->has_accelerometer_x() || !imu->has_accelerometer_y() ||
+ !imu->has_accelerometer_z()) {
+ return;
+ }
+
+ VLOG(2) << "IMU " << imu;
imu->UnPackTo(&last_value_);
Eigen::Vector3d gyro(last_value_.gyro_x, last_value_.gyro_y,
last_value_.gyro_z);
diff --git a/frc971/vision/calibration_accumulator.h b/frc971/vision/calibration_accumulator.h
index e4f9c8a..d21f4c6 100644
--- a/frc971/vision/calibration_accumulator.h
+++ b/frc971/vision/calibration_accumulator.h
@@ -56,7 +56,9 @@
size_t camera_samples_size() const { return rot_trans_points_.size(); }
- size_t turret_samples() const { return turret_points_.size(); }
+ size_t imu_samples_size() const { return imu_points_.size(); }
+
+ size_t turret_samples_size() const { return turret_points_.size(); }
private:
std::vector<std::pair<aos::distributed_clock::time_point,
@@ -82,14 +84,18 @@
aos::EventLoop *image_event_loop, aos::EventLoop *imu_event_loop,
std::string_view pi, CalibrationData *data);
- // Processes a charuco detection.
+ // Processes a charuco detection that is returned from charuco_lib.
+ // For a valid detection(s), it stores camera observation
+ // Also optionally displays and saves annotated images based on visualize and
+ // save_path flags, respectively
void HandleCharuco(cv::Mat rgb_image,
const aos::monotonic_clock::time_point eof,
- std::vector<int> /*charuco_ids*/,
- std::vector<cv::Point2f> /*charuco_corners*/, bool valid,
- Eigen::Vector3d rvec_eigen, Eigen::Vector3d tvec_eigen);
+ std::vector<cv::Vec4i> /*charuco_ids*/,
+ std::vector<std::vector<cv::Point2f>> /*charuco_corners*/,
+ bool valid, std::vector<Eigen::Vector3d> rvecs_eigen,
+ std::vector<Eigen::Vector3d> tvecs_eigen);
- // Processes an IMU reading.
+ // Processes an IMU reading by storing for later processing
void HandleIMU(const frc971::IMUValues *imu);
private:
@@ -99,6 +105,7 @@
aos::NodeEventLoopFactory *imu_factory_;
CharucoExtractor charuco_extractor_;
+ ImageCallback image_callback_;
CalibrationData *data_;
diff --git a/frc971/vision/ceres/BUILD b/frc971/vision/ceres/BUILD
new file mode 100644
index 0000000..5622b55
--- /dev/null
+++ b/frc971/vision/ceres/BUILD
@@ -0,0 +1,24 @@
+# Copy of ceres example code from their repo
+cc_library(
+ name = "pose_graph_2d_lib",
+ hdrs = [
+ "angle_local_parameterization.h",
+ "normalize_angle.h",
+ "pose_graph_2d_error_term.h",
+ "read_g2o.h",
+ "types.h",
+ ],
+ copts = [
+ # Needed to silence GFlags complaints.
+ "-Wno-sign-compare",
+ "-Wno-unused-parameter",
+ "-Wno-format-nonliteral",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "@com_github_gflags_gflags//:gflags",
+ "@com_google_ceres_solver//:ceres",
+ "@org_tuxfamily_eigen//:eigen",
+ ],
+)
diff --git a/frc971/vision/ceres/angle_local_parameterization.h b/frc971/vision/ceres/angle_local_parameterization.h
new file mode 100644
index 0000000..a81637c
--- /dev/null
+++ b/frc971/vision/ceres/angle_local_parameterization.h
@@ -0,0 +1,64 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2016 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: vitus@google.com (Michael Vitus)
+
+#ifndef CERES_EXAMPLES_POSE_GRAPH_2D_ANGLE_LOCAL_PARAMETERIZATION_H_
+#define CERES_EXAMPLES_POSE_GRAPH_2D_ANGLE_LOCAL_PARAMETERIZATION_H_
+
+#include "ceres/local_parameterization.h"
+#include "normalize_angle.h"
+
+namespace ceres {
+namespace examples {
+
+// Defines a local parameterization for updating the angle to be constrained in
+// [-pi to pi).
+class AngleLocalParameterization {
+ public:
+ template <typename T>
+ bool operator()(const T* theta_radians,
+ const T* delta_theta_radians,
+ T* theta_radians_plus_delta) const {
+ *theta_radians_plus_delta =
+ NormalizeAngle(*theta_radians + *delta_theta_radians);
+
+ return true;
+ }
+
+ static ceres::LocalParameterization* Create() {
+ return (new ceres::AutoDiffLocalParameterization<AngleLocalParameterization,
+ 1,
+ 1>);
+ }
+};
+
+} // namespace examples
+} // namespace ceres
+
+#endif // CERES_EXAMPLES_POSE_GRAPH_2D_ANGLE_LOCAL_PARAMETERIZATION_H_
diff --git a/frc971/vision/ceres/normalize_angle.h b/frc971/vision/ceres/normalize_angle.h
new file mode 100644
index 0000000..c215671
--- /dev/null
+++ b/frc971/vision/ceres/normalize_angle.h
@@ -0,0 +1,53 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2016 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: vitus@google.com (Michael Vitus)
+
+#ifndef CERES_EXAMPLES_POSE_GRAPH_2D_NORMALIZE_ANGLE_H_
+#define CERES_EXAMPLES_POSE_GRAPH_2D_NORMALIZE_ANGLE_H_
+
+#include <cmath>
+
+#include "ceres/ceres.h"
+
+namespace ceres {
+namespace examples {
+
+// Normalizes the angle in radians between [-pi and pi).
+template <typename T>
+inline T NormalizeAngle(const T& angle_radians) {
+ // Use ceres::floor because it is specialized for double and Jet types.
+ T two_pi(2.0 * M_PI);
+ return angle_radians -
+ two_pi * ceres::floor((angle_radians + T(M_PI)) / two_pi);
+}
+
+} // namespace examples
+} // namespace ceres
+
+#endif // CERES_EXAMPLES_POSE_GRAPH_2D_NORMALIZE_ANGLE_H_
diff --git a/frc971/vision/ceres/pose_graph_2d_error_term.h b/frc971/vision/ceres/pose_graph_2d_error_term.h
new file mode 100644
index 0000000..2df31f6
--- /dev/null
+++ b/frc971/vision/ceres/pose_graph_2d_error_term.h
@@ -0,0 +1,119 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2016 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: vitus@google.com (Michael Vitus)
+//
+// Cost function for a 2D pose graph formulation.
+
+#ifndef CERES_EXAMPLES_POSE_GRAPH_2D_POSE_GRAPH_2D_ERROR_TERM_H_
+#define CERES_EXAMPLES_POSE_GRAPH_2D_POSE_GRAPH_2D_ERROR_TERM_H_
+
+#include "Eigen/Core"
+
+namespace ceres {
+namespace examples {
+
+template <typename T>
+Eigen::Matrix<T, 2, 2> RotationMatrix2D(T yaw_radians) {
+ const T cos_yaw = ceres::cos(yaw_radians);
+ const T sin_yaw = ceres::sin(yaw_radians);
+
+ Eigen::Matrix<T, 2, 2> rotation;
+ rotation << cos_yaw, -sin_yaw, sin_yaw, cos_yaw;
+ return rotation;
+}
+
+// Computes the error term for two poses that have a relative pose measurement
+// between them. Let the hat variables be the measurement.
+//
+// residual = information^{1/2} * [ r_a^T * (p_b - p_a) - \hat{p_ab} ]
+// [ Normalize(yaw_b - yaw_a - \hat{yaw_ab}) ]
+//
+// where r_a is the rotation matrix that rotates a vector represented in frame A
+// into the global frame, and Normalize(*) ensures the angles are in the range
+// [-pi, pi).
+class PoseGraph2dErrorTerm {
+ public:
+ PoseGraph2dErrorTerm(double x_ab,
+ double y_ab,
+ double yaw_ab_radians,
+ const Eigen::Matrix3d& sqrt_information)
+ : p_ab_(x_ab, y_ab),
+ yaw_ab_radians_(yaw_ab_radians),
+ sqrt_information_(sqrt_information) {}
+
+ template <typename T>
+ bool operator()(const T* const x_a,
+ const T* const y_a,
+ const T* const yaw_a,
+ const T* const x_b,
+ const T* const y_b,
+ const T* const yaw_b,
+ T* residuals_ptr) const {
+ const Eigen::Matrix<T, 2, 1> p_a(*x_a, *y_a);
+ const Eigen::Matrix<T, 2, 1> p_b(*x_b, *y_b);
+
+ Eigen::Map<Eigen::Matrix<T, 3, 1>> residuals_map(residuals_ptr);
+
+ residuals_map.template head<2>() =
+ RotationMatrix2D(*yaw_a).transpose() * (p_b - p_a) - p_ab_.cast<T>();
+ residuals_map(2) = ceres::examples::NormalizeAngle(
+ (*yaw_b - *yaw_a) - static_cast<T>(yaw_ab_radians_));
+
+ // Scale the residuals by the square root information matrix to account for
+ // the measurement uncertainty.
+ residuals_map = sqrt_information_.template cast<T>() * residuals_map;
+
+ return true;
+ }
+
+ static ceres::CostFunction* Create(double x_ab,
+ double y_ab,
+ double yaw_ab_radians,
+ const Eigen::Matrix3d& sqrt_information) {
+ return (new ceres::
+ AutoDiffCostFunction<PoseGraph2dErrorTerm, 3, 1, 1, 1, 1, 1, 1>(
+ new PoseGraph2dErrorTerm(
+ x_ab, y_ab, yaw_ab_radians, sqrt_information)));
+ }
+
+ EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+
+ private:
+ // The position of B relative to A in the A frame.
+ const Eigen::Vector2d p_ab_;
+ // The orientation of frame B relative to frame A.
+ const double yaw_ab_radians_;
+ // The inverse square root of the measurement covariance matrix.
+ const Eigen::Matrix3d sqrt_information_;
+};
+
+} // namespace examples
+} // namespace ceres
+
+#endif // CERES_EXAMPLES_POSE_GRAPH_2D_POSE_GRAPH_2D_ERROR_TERM_H_
diff --git a/frc971/vision/ceres/read_g2o.h b/frc971/vision/ceres/read_g2o.h
new file mode 100644
index 0000000..fea32e9
--- /dev/null
+++ b/frc971/vision/ceres/read_g2o.h
@@ -0,0 +1,143 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2016 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: vitus@google.com (Michael Vitus)
+//
+// Reads a file in the g2o filename format that describes a pose graph problem.
+
+#ifndef EXAMPLES_CERES_READ_G2O_H_
+#define EXAMPLES_CERES_READ_G2O_H_
+
+#include <fstream>
+#include <string>
+
+#include "glog/logging.h"
+
+namespace ceres {
+namespace examples {
+
+// Reads a single pose from the input and inserts it into the map. Returns false
+// if there is a duplicate entry.
+template <typename Pose, typename Allocator>
+bool ReadVertex(std::ifstream* infile,
+ std::map<int, Pose, std::less<int>, Allocator>* poses) {
+ int id;
+ Pose pose;
+ *infile >> id >> pose;
+
+ // Ensure we don't have duplicate poses.
+ if (poses->find(id) != poses->end()) {
+ LOG(ERROR) << "Duplicate vertex with ID: " << id;
+ return false;
+ }
+ (*poses)[id] = pose;
+
+ return true;
+}
+
+// Reads the contraints between two vertices in the pose graph
+template <typename Constraint, typename Allocator>
+void ReadConstraint(std::ifstream* infile,
+ std::vector<Constraint, Allocator>* constraints) {
+ Constraint constraint;
+ *infile >> constraint;
+
+ constraints->push_back(constraint);
+}
+
+// Reads a file in the g2o filename format that describes a pose graph
+// problem. The g2o format consists of two entries, vertices and constraints.
+//
+// In 2D, a vertex is defined as follows:
+//
+// VERTEX_SE2 ID x_meters y_meters yaw_radians
+//
+// A constraint is defined as follows:
+//
+// EDGE_SE2 ID_A ID_B A_x_B A_y_B A_yaw_B I_11 I_12 I_13 I_22 I_23 I_33
+//
+// where I_ij is the (i, j)-th entry of the information matrix for the
+// measurement.
+//
+//
+// In 3D, a vertex is defined as follows:
+//
+// VERTEX_SE3:QUAT ID x y z q_x q_y q_z q_w
+//
+// where the quaternion is in Hamilton form.
+// A constraint is defined as follows:
+//
+// EDGE_SE3:QUAT ID_a ID_b x_ab y_ab z_ab q_x_ab q_y_ab q_z_ab q_w_ab I_11 I_12 I_13 ... I_16 I_22 I_23 ... I_26 ... I_66 // NOLINT
+//
+// where I_ij is the (i, j)-th entry of the information matrix for the
+// measurement. Only the upper-triangular part is stored. The measurement order
+// is the delta position followed by the delta orientation.
+template <typename Pose,
+ typename Constraint,
+ typename MapAllocator,
+ typename VectorAllocator>
+bool ReadG2oFile(const std::string& filename,
+ std::map<int, Pose, std::less<int>, MapAllocator>* poses,
+ std::vector<Constraint, VectorAllocator>* constraints) {
+ CHECK(poses != NULL);
+ CHECK(constraints != NULL);
+
+ poses->clear();
+ constraints->clear();
+
+ std::ifstream infile(filename.c_str());
+ if (!infile) {
+ return false;
+ }
+
+ std::string data_type;
+ while (infile.good()) {
+ // Read whether the type is a node or a constraint.
+ infile >> data_type;
+ if (data_type == Pose::name()) {
+ if (!ReadVertex(&infile, poses)) {
+ return false;
+ }
+ } else if (data_type == Constraint::name()) {
+ ReadConstraint(&infile, constraints);
+ } else {
+ LOG(ERROR) << "Unknown data type: " << data_type;
+ return false;
+ }
+
+ // Clear any trailing whitespace from the line.
+ infile >> std::ws;
+ }
+
+ return true;
+}
+
+} // namespace examples
+} // namespace ceres
+
+#endif // EXAMPLES_CERES_READ_G2O_H_
diff --git a/frc971/vision/ceres/types.h b/frc971/vision/ceres/types.h
new file mode 100644
index 0000000..3c13824
--- /dev/null
+++ b/frc971/vision/ceres/types.h
@@ -0,0 +1,101 @@
+// Ceres Solver - A fast non-linear least squares minimizer
+// Copyright 2016 Google Inc. All rights reserved.
+// http://ceres-solver.org/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors may be
+// used to endorse or promote products derived from this software without
+// specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+// Author: vitus@google.com (Michael Vitus)
+//
+// Defines the types used in the 2D pose graph SLAM formulation. Each vertex of
+// the graph has a unique integer ID with a position and orientation. There are
+// delta transformation constraints between two vertices.
+
+#ifndef CERES_EXAMPLES_POSE_GRAPH_2D_TYPES_H_
+#define CERES_EXAMPLES_POSE_GRAPH_2D_TYPES_H_
+
+#include <fstream>
+
+#include "Eigen/Core"
+#include "normalize_angle.h"
+
+namespace ceres {
+namespace examples {
+
+// The state for each vertex in the pose graph.
+struct Pose2d {
+ double x;
+ double y;
+ double yaw_radians;
+
+ // The name of the data type in the g2o file format.
+ static std::string name() { return "VERTEX_SE2"; }
+};
+
+inline std::istream& operator>>(std::istream& input, Pose2d& pose) {
+ input >> pose.x >> pose.y >> pose.yaw_radians;
+ // Normalize the angle between -pi to pi.
+ pose.yaw_radians = NormalizeAngle(pose.yaw_radians);
+ return input;
+}
+
+// The constraint between two vertices in the pose graph. The constraint is the
+// transformation from vertex id_begin to vertex id_end.
+struct Constraint2d {
+ int id_begin;
+ int id_end;
+
+ double x;
+ double y;
+ double yaw_radians;
+
+ // The inverse of the covariance matrix for the measurement. The order of the
+ // entries are x, y, and yaw.
+ Eigen::Matrix3d information;
+
+ // The name of the data type in the g2o file format.
+ static std::string name() { return "EDGE_SE2"; }
+};
+
+inline std::istream& operator>>(std::istream& input, Constraint2d& constraint) {
+ input >> constraint.id_begin >> constraint.id_end >> constraint.x >>
+ constraint.y >> constraint.yaw_radians >> constraint.information(0, 0) >>
+ constraint.information(0, 1) >> constraint.information(0, 2) >>
+ constraint.information(1, 1) >> constraint.information(1, 2) >>
+ constraint.information(2, 2);
+
+ // Set the lower triangular part of the information matrix.
+ constraint.information(1, 0) = constraint.information(0, 1);
+ constraint.information(2, 0) = constraint.information(0, 2);
+ constraint.information(2, 1) = constraint.information(1, 2);
+
+ // Normalize the angle between -pi to pi.
+ constraint.yaw_radians = NormalizeAngle(constraint.yaw_radians);
+ return input;
+}
+
+} // namespace examples
+} // namespace ceres
+
+#endif // CERES_EXAMPLES_POSE_GRAPH_2D_TYPES_H_
diff --git a/frc971/vision/charuco_lib.cc b/frc971/vision/charuco_lib.cc
index fde5394..0db571f 100644
--- a/frc971/vision/charuco_lib.cc
+++ b/frc971/vision/charuco_lib.cc
@@ -17,13 +17,17 @@
#include "y2020/vision/sift/sift_training_generated.h"
#include "y2020/vision/tools/python_code/sift_training_data.h"
-DEFINE_uint32(min_targets, 10,
- "The mininum number of targets required to match.");
-DEFINE_bool(large_board, true, "If true, use the large calibration board.");
-DEFINE_bool(coarse_pattern, true, "If true, use coarse arucos; else, use fine");
DEFINE_string(board_template_path, "",
"If specified, write an image to the specified path for the "
"charuco board pattern.");
+DEFINE_bool(coarse_pattern, true, "If true, use coarse arucos; else, use fine");
+DEFINE_bool(large_board, true, "If true, use the large calibration board.");
+DEFINE_uint32(
+ min_charucos, 10,
+ "The mininum number of aruco targets in charuco board required to match.");
+DEFINE_string(target_type, "charuco",
+ "Type of target: april_tag|aruco|charuco|charuco_diamond");
+DEFINE_bool(visualize, false, "Whether to visualize the resulting data.");
namespace frc971 {
namespace vision {
@@ -87,7 +91,8 @@
ImageCallback::ImageCallback(
aos::EventLoop *event_loop, std::string_view channel,
- std::function<void(cv::Mat, monotonic_clock::time_point)> &&fn)
+ std::function<void(cv::Mat, monotonic_clock::time_point)> &&handle_image_fn)
+
: event_loop_(event_loop),
server_fetcher_(
event_loop_->MakeFetcher<aos::message_bridge::ServerStatistics>(
@@ -97,7 +102,7 @@
event_loop_->GetChannel<CameraImage>(channel)
->source_node()
->string_view())),
- handle_image_(std::move(fn)) {
+ handle_image_(std::move(handle_image_fn)) {
event_loop_->MakeWatcher(channel, [this](const CameraImage &image) {
const monotonic_clock::time_point eof_source_node =
monotonic_clock::time_point(
@@ -150,47 +155,142 @@
});
}
+void CharucoExtractor::SetupTargetData() {
+ // TODO(Jim): Put correct values here
+ marker_length_ = 0.15;
+ square_length_ = 0.1651;
+
+ // Only charuco board has a board associated with it
+ board_ = static_cast<cv::Ptr<cv::aruco::CharucoBoard>>(NULL);
+
+ if (FLAGS_target_type == "charuco" || FLAGS_target_type == "aruco") {
+ dictionary_ = cv::aruco::getPredefinedDictionary(
+ FLAGS_large_board ? cv::aruco::DICT_5X5_250 : cv::aruco::DICT_6X6_250);
+
+ if (FLAGS_target_type == "charuco") {
+ LOG(INFO) << "Using " << (FLAGS_large_board ? " large " : " small ")
+ << " charuco board with "
+ << (FLAGS_coarse_pattern ? "coarse" : "fine") << " pattern";
+ board_ =
+ (FLAGS_large_board
+ ? (FLAGS_coarse_pattern ? cv::aruco::CharucoBoard::create(
+ 12, 9, 0.06, 0.04666, dictionary_)
+ : cv::aruco::CharucoBoard::create(
+ 25, 18, 0.03, 0.0233, dictionary_))
+ : (FLAGS_coarse_pattern ? cv::aruco::CharucoBoard::create(
+ 7, 5, 0.04, 0.025, dictionary_)
+ // TODO(jim): Need to figure out what
+ // size is for small board, fine pattern
+ : cv::aruco::CharucoBoard::create(
+ 7, 5, 0.03, 0.0233, dictionary_)));
+ if (!FLAGS_board_template_path.empty()) {
+ cv::Mat board_image;
+ board_->draw(cv::Size(600, 500), board_image, 10, 1);
+ cv::imwrite(FLAGS_board_template_path, board_image);
+ }
+ }
+ } else if (FLAGS_target_type == "charuco_diamond") {
+ // TODO<Jim>: Measure this
+ marker_length_ = 0.15;
+ square_length_ = 0.1651;
+ dictionary_ = cv::aruco::getPredefinedDictionary(cv::aruco::DICT_4X4_250);
+ } else if (FLAGS_target_type == "april_tag") {
+ // Current printout is supposed to be 200mm
+ // TODO<Jim>: Verify this
+ square_length_ = 0.2;
+ dictionary_ =
+ cv::aruco::getPredefinedDictionary(cv::aruco::DICT_APRILTAG_36h11);
+ } else {
+ // Bail out if it's not a supported target
+ LOG(FATAL) << "Target type undefined: " << FLAGS_target_type
+ << " vs. april_tag|aruco|charuco|charuco_diamond";
+ }
+}
+
+void CharucoExtractor::DrawTargetPoses(cv::Mat rgb_image,
+ std::vector<cv::Vec3d> rvecs,
+ std::vector<cv::Vec3d> tvecs) {
+ const Eigen::Matrix<double, 3, 4> camera_projection =
+ Eigen::Matrix<double, 3, 4>::Identity();
+
+ int x_coord = 10;
+ int y_coord = 0;
+ // draw axis for each marker
+ for (uint i = 0; i < rvecs.size(); i++) {
+ Eigen::Vector3d rvec_eigen, tvec_eigen;
+ cv::cv2eigen(rvecs[i], rvec_eigen);
+ cv::cv2eigen(tvecs[i], tvec_eigen);
+
+ Eigen::Quaternion<double> rotation(
+ frc971::controls::ToQuaternionFromRotationVector(rvec_eigen));
+ Eigen::Translation3d translation(tvec_eigen);
+
+ const Eigen::Affine3d board_to_camera = translation * rotation;
+
+ Eigen::Vector3d result = eigen_camera_matrix_ * camera_projection *
+ board_to_camera * Eigen::Vector3d::Zero();
+
+ // Found that drawAxis hangs if you try to draw with z values too
+ // small (trying to draw axes at inifinity)
+ // TODO<Jim>: Explore what real thresholds for this should be;
+ // likely Don't need to get rid of negative values
+ if (result.z() < 0.01) {
+ LOG(INFO) << "Skipping, due to z value too small: " << result.z();
+ } else {
+ result /= result.z();
+ if (FLAGS_target_type == "charuco") {
+ cv::aruco::drawAxis(rgb_image, camera_matrix_, dist_coeffs_, rvecs[i],
+ tvecs[i], 0.1);
+ } else {
+ cv::drawFrameAxes(rgb_image, camera_matrix_, dist_coeffs_, rvecs[i],
+ tvecs[i], 0.1);
+ }
+ }
+ std::stringstream ss;
+ ss << "tvec[" << i << "] = " << tvecs[i];
+ y_coord += 25;
+ cv::putText(rgb_image, ss.str(), cv::Point(x_coord, y_coord),
+ cv::FONT_HERSHEY_PLAIN, 1.0, cv::Scalar(255, 255, 255));
+ ss.str("");
+ ss << "rvec[" << i << "] = " << rvecs[i];
+ y_coord += 25;
+ cv::putText(rgb_image, ss.str(), cv::Point(x_coord, y_coord),
+ cv::FONT_HERSHEY_PLAIN, 1.0, cv::Scalar(255, 255, 255));
+ }
+}
+
+void CharucoExtractor::PackPoseResults(
+ std::vector<cv::Vec3d> &rvecs, std::vector<cv::Vec3d> &tvecs,
+ std::vector<Eigen::Vector3d> *rvecs_eigen,
+ std::vector<Eigen::Vector3d> *tvecs_eigen) {
+ for (cv::Vec3d rvec : rvecs) {
+ Eigen::Vector3d rvec_eigen = Eigen::Vector3d::Zero();
+ cv::cv2eigen(rvec, rvec_eigen);
+ rvecs_eigen->emplace_back(rvec_eigen);
+ }
+
+ for (cv::Vec3d tvec : tvecs) {
+ Eigen::Vector3d tvec_eigen = Eigen::Vector3d::Zero();
+ cv::cv2eigen(tvec, tvec_eigen);
+ tvecs_eigen->emplace_back(tvec_eigen);
+ }
+}
+
CharucoExtractor::CharucoExtractor(
aos::EventLoop *event_loop, std::string_view pi,
- std::function<void(cv::Mat, monotonic_clock::time_point, std::vector<int>,
- std::vector<cv::Point2f>, bool, Eigen::Vector3d,
- Eigen::Vector3d)> &&fn)
+ std::function<void(cv::Mat, monotonic_clock::time_point,
+ std::vector<cv::Vec4i>,
+ std::vector<std::vector<cv::Point2f>>, bool,
+ std::vector<Eigen::Vector3d>,
+ std::vector<Eigen::Vector3d>)> &&handle_charuco_fn)
: event_loop_(event_loop),
calibration_(SiftTrainingData(), pi),
- dictionary_(cv::aruco::getPredefinedDictionary(
- FLAGS_large_board ? cv::aruco::DICT_5X5_250
- : cv::aruco::DICT_6X6_250)),
- board_(
- FLAGS_large_board
- ? (FLAGS_coarse_pattern ? cv::aruco::CharucoBoard::create(
- 12, 9, 0.06, 0.04666, dictionary_)
- : cv::aruco::CharucoBoard::create(
- 25, 18, 0.03, 0.0233, dictionary_))
- : (FLAGS_coarse_pattern ? cv::aruco::CharucoBoard::create(
- 7, 5, 0.04, 0.025, dictionary_)
- // TODO(jim): Need to figure out what size
- // is for small board, fine pattern
- : cv::aruco::CharucoBoard::create(
- 7, 5, 0.03, 0.0233, dictionary_))),
camera_matrix_(calibration_.CameraIntrinsics()),
eigen_camera_matrix_(calibration_.CameraIntrinsicsEigen()),
dist_coeffs_(calibration_.CameraDistCoeffs()),
pi_number_(aos::network::ParsePiNumber(pi)),
- image_callback_(
- event_loop,
- absl::StrCat("/pi", std::to_string(pi_number_.value()), "/camera"),
- [this](cv::Mat rgb_image, const monotonic_clock::time_point eof) {
- HandleImage(rgb_image, eof);
- }),
- handle_charuco_(std::move(fn)) {
- LOG(INFO) << "Using " << (FLAGS_large_board ? "large" : "small")
- << " board with " << (FLAGS_coarse_pattern ? "coarse" : "fine")
- << " pattern";
- if (!FLAGS_board_template_path.empty()) {
- cv::Mat board_image;
- board_->draw(cv::Size(600, 500), board_image, 10, 1);
- cv::imwrite(FLAGS_board_template_path, board_image);
- }
+ handle_charuco_(std::move(handle_charuco_fn)) {
+ SetupTargetData();
LOG(INFO) << "Camera matrix " << camera_matrix_;
LOG(INFO) << "Distortion Coefficients " << dist_coeffs_;
@@ -207,91 +307,143 @@
std::chrono::duration_cast<std::chrono::duration<double>>(
event_loop_->monotonic_now() - eof)
.count();
+
+ // Set up the variables we'll use in the callback function
+ bool valid = false;
+ // Return a list of poses; for Charuco Board there will be just one
+ std::vector<Eigen::Vector3d> rvecs_eigen;
+ std::vector<Eigen::Vector3d> tvecs_eigen;
+
+ // ids and corners for initial aruco marker detections
std::vector<int> marker_ids;
std::vector<std::vector<cv::Point2f>> marker_corners;
- cv::aruco::detectMarkers(rgb_image, board_->dictionary, marker_corners,
- marker_ids);
+ // ids and corners for final, refined board / marker detections
+ // Using Vec4i type since it supports Charuco Diamonds
+ // And overloading it using 1st int in Vec4i for others target types
+ std::vector<cv::Vec4i> result_ids;
+ std::vector<std::vector<cv::Point2f>> result_corners;
- std::vector<cv::Point2f> charuco_corners;
- std::vector<int> charuco_ids;
- bool valid = false;
- Eigen::Vector3d rvec_eigen = Eigen::Vector3d::Zero();
- Eigen::Vector3d tvec_eigen = Eigen::Vector3d::Zero();
+ // Do initial marker detection; this is the same for all target types
+ cv::aruco::detectMarkers(rgb_image, dictionary_, marker_corners, marker_ids);
+ cv::aruco::drawDetectedMarkers(rgb_image, marker_corners, marker_ids);
- // If at least one marker detected
- if (marker_ids.size() >= FLAGS_min_targets) {
- // Run everything twice, once with the calibration, and once
- // without. This lets us both calibrate, and also print out the pose
- // real time with the previous calibration.
- cv::aruco::interpolateCornersCharuco(marker_corners, marker_ids, rgb_image,
- board_, charuco_corners, charuco_ids);
+ VLOG(2) << "Handle Image, with target type = " << FLAGS_target_type << " and "
+ << marker_ids.size() << " markers detected initially";
- std::vector<cv::Point2f> charuco_corners_with_calibration;
- std::vector<int> charuco_ids_with_calibration;
+ if (marker_ids.size() == 0) {
+ VLOG(2) << "Didn't find any markers";
+ } else {
+ if (FLAGS_target_type == "charuco") {
+ std::vector<int> charuco_ids;
+ std::vector<cv::Point2f> charuco_corners;
- cv::aruco::interpolateCornersCharuco(
- marker_corners, marker_ids, rgb_image, board_,
- charuco_corners_with_calibration, charuco_ids_with_calibration,
- camera_matrix_, dist_coeffs_);
+ // If enough aruco markers detected for the Charuco board
+ if (marker_ids.size() >= FLAGS_min_charucos) {
+ // Run everything twice, once with the calibration, and once
+ // without. This lets us both collect data to calibrate the
+ // intrinsics of the camera (to determine the intrinsics from
+ // multiple samples), and also to use data from a previous/stored
+ // calibration to determine a more accurate pose in real time (used
+ // for extrinsics calibration)
+ cv::aruco::interpolateCornersCharuco(marker_corners, marker_ids,
+ rgb_image, board_, charuco_corners,
+ charuco_ids);
- cv::aruco::drawDetectedMarkers(rgb_image, marker_corners, marker_ids);
+ std::vector<cv::Point2f> charuco_corners_with_calibration;
+ std::vector<int> charuco_ids_with_calibration;
- if (charuco_ids.size() >= FLAGS_min_targets) {
- cv::aruco::drawDetectedCornersCharuco(rgb_image, charuco_corners,
- charuco_ids, cv::Scalar(255, 0, 0));
+ // This call uses a previous intrinsic calibration to get more
+ // accurate marker locations, for a better pose estimate
+ cv::aruco::interpolateCornersCharuco(
+ marker_corners, marker_ids, rgb_image, board_,
+ charuco_corners_with_calibration, charuco_ids_with_calibration,
+ camera_matrix_, dist_coeffs_);
- cv::Vec3d rvec, tvec;
- valid = cv::aruco::estimatePoseCharucoBoard(
- charuco_corners_with_calibration, charuco_ids_with_calibration,
- board_, camera_matrix_, dist_coeffs_, rvec, tvec);
+ if (charuco_ids.size() >= FLAGS_min_charucos) {
+ cv::aruco::drawDetectedCornersCharuco(
+ rgb_image, charuco_corners, charuco_ids, cv::Scalar(255, 0, 0));
- // if charuco pose is valid
- if (valid) {
- cv::cv2eigen(rvec, rvec_eigen);
- cv::cv2eigen(tvec, tvec_eigen);
+ cv::Vec3d rvec, tvec;
+ valid = cv::aruco::estimatePoseCharucoBoard(
+ charuco_corners_with_calibration, charuco_ids_with_calibration,
+ board_, camera_matrix_, dist_coeffs_, rvec, tvec);
- Eigen::Quaternion<double> rotation(
- frc971::controls::ToQuaternionFromRotationVector(rvec_eigen));
- Eigen::Translation3d translation(tvec_eigen);
+ // if charuco pose is valid, return pose, with ids and corners
+ if (valid) {
+ std::vector<cv::Vec3d> rvecs, tvecs;
+ rvecs.emplace_back(rvec);
+ tvecs.emplace_back(tvec);
+ DrawTargetPoses(rgb_image, rvecs, tvecs);
- const Eigen::Affine3d board_to_camera = translation * rotation;
-
- Eigen::Matrix<double, 3, 4> camera_projection =
- Eigen::Matrix<double, 3, 4>::Identity();
- Eigen::Vector3d result = eigen_camera_matrix_ * camera_projection *
- board_to_camera * Eigen::Vector3d::Zero();
-
- // Found that drawAxis hangs if you try to draw with z values too small
- // (trying to draw axes at inifinity)
- // TODO<Jim>: Explore what real thresholds for this should be; likely
- // Don't need to get rid of negative values
- if (result.z() < 0.01) {
- LOG(INFO) << "Skipping, due to z value too small: " << result.z();
- valid = false;
+ PackPoseResults(rvecs, tvecs, &rvecs_eigen, &tvecs_eigen);
+ // Store the corners without calibration, since we use them to
+ // do calibration
+ result_corners.emplace_back(charuco_corners);
+ for (auto id : charuco_ids) {
+ result_ids.emplace_back(cv::Vec4i{id, 0, 0, 0});
+ }
+ } else {
+ VLOG(2) << "Age: " << age_double << ", invalid charuco board pose";
+ }
} else {
- result /= result.z();
- cv::circle(rgb_image, cv::Point(result.x(), result.y()), 4,
- cv::Scalar(255, 255, 255), 0, cv::LINE_8);
-
- cv::aruco::drawAxis(rgb_image, camera_matrix_, dist_coeffs_, rvec,
- tvec, 0.1);
+ VLOG(2) << "Age: " << age_double << ", not enough charuco IDs, got "
+ << charuco_ids.size() << ", needed " << FLAGS_min_charucos;
}
} else {
- LOG(INFO) << "Age: " << age_double << ", invalid pose";
+ VLOG(2) << "Age: " << age_double
+ << ", not enough marker IDs for charuco board, got "
+ << marker_ids.size() << ", needed " << FLAGS_min_charucos;
+ }
+ } else if (FLAGS_target_type == "april_tag" ||
+ FLAGS_target_type == "aruco") {
+ // estimate pose for april tags doesn't return valid, so marking true
+ valid = true;
+ std::vector<cv::Vec3d> rvecs, tvecs;
+ cv::aruco::estimatePoseSingleMarkers(marker_corners, square_length_,
+ camera_matrix_, dist_coeffs_, rvecs,
+ tvecs);
+ DrawTargetPoses(rgb_image, rvecs, tvecs);
+
+ PackPoseResults(rvecs, tvecs, &rvecs_eigen, &tvecs_eigen);
+ for (uint i = 0; i < marker_ids.size(); i++) {
+ result_ids.emplace_back(cv::Vec4i{marker_ids[i], 0, 0, 0});
+ }
+ result_corners = marker_corners;
+ } else if (FLAGS_target_type == "charuco_diamond") {
+ // Extract the diamonds associated with the markers
+ std::vector<cv::Vec4i> diamond_ids;
+ std::vector<std::vector<cv::Point2f>> diamond_corners;
+ cv::aruco::detectCharucoDiamond(rgb_image, marker_corners, marker_ids,
+ square_length_ / marker_length_,
+ diamond_corners, diamond_ids);
+
+ // Check to see if we found any diamond targets
+ if (diamond_ids.size() > 0) {
+ cv::aruco::drawDetectedDiamonds(rgb_image, diamond_corners,
+ diamond_ids);
+
+ // estimate pose for diamonds doesn't return valid, so marking true
+ valid = true;
+ std::vector<cv::Vec3d> rvecs, tvecs;
+ cv::aruco::estimatePoseSingleMarkers(diamond_corners, square_length_,
+ camera_matrix_, dist_coeffs_,
+ rvecs, tvecs);
+ DrawTargetPoses(rgb_image, rvecs, tvecs);
+
+ PackPoseResults(rvecs, tvecs, &rvecs_eigen, &tvecs_eigen);
+ result_ids = diamond_ids;
+ result_corners = diamond_corners;
+ } else {
+ VLOG(2) << "Found aruco markers, but no charuco diamond targets";
}
} else {
- VLOG(2) << "Age: " << age_double << ", not enough charuco IDs, got "
- << charuco_ids.size() << ", needed " << FLAGS_min_targets;
+ LOG(FATAL) << "Unknown target type: " << FLAGS_target_type;
}
- } else {
- VLOG(2) << "Age: " << age_double << ", not enough marker IDs, got "
- << marker_ids.size() << ", needed " << FLAGS_min_targets;
- cv::aruco::drawDetectedMarkers(rgb_image, marker_corners, marker_ids);
}
- handle_charuco_(rgb_image, eof, charuco_ids, charuco_corners, valid,
- rvec_eigen, tvec_eigen);
+ handle_charuco_(rgb_image, eof, result_ids, result_corners, valid,
+ rvecs_eigen, tvecs_eigen);
}
} // namespace vision
diff --git a/frc971/vision/charuco_lib.h b/frc971/vision/charuco_lib.h
index a54bfca..362bb7d 100644
--- a/frc971/vision/charuco_lib.h
+++ b/frc971/vision/charuco_lib.h
@@ -15,6 +15,9 @@
#include "y2020/vision/sift/sift_generated.h"
#include "y2020/vision/sift/sift_training_generated.h"
+DECLARE_bool(visualize);
+DECLARE_string(target_type);
+
namespace frc971 {
namespace vision {
@@ -41,14 +44,16 @@
const sift::CameraCalibration *camera_calibration_;
};
-// Class to call a function with a cv::Mat and age when an image shows up on the
-// provided channel. This hides all the conversions and wrangling needed to
-// view the image.
+// Helper class to call a function with a cv::Mat and age when an image shows up
+// on the provided channel. This hides all the conversions and wrangling needed
+// to view the image.
+// Can connect this with HandleImage function from CharucoExtrator for
+// full-service callback functionality
class ImageCallback {
public:
- ImageCallback(
- aos::EventLoop *event_loop, std::string_view channel,
- std::function<void(cv::Mat, aos::monotonic_clock::time_point)> &&fn);
+ ImageCallback(aos::EventLoop *event_loop, std::string_view channel,
+ std::function<void(cv::Mat, aos::monotonic_clock::time_point)>
+ &&handle_image_fn);
private:
aos::EventLoop *event_loop_;
@@ -65,16 +70,25 @@
// cv::Mat -> image with overlays drawn on it.
// monotonic_clock::time_point -> Time on this node when this image was
// captured.
- // std::vector<int> -> charuco_ids
- // std::vector<cv::Point2f> -> charuco_corners
+ // std::vector<Vec4i> -> target ids (aruco/april in first slot of Vec4i)
+ // NOTE: We use Vec4i since that stores the ids for the charuco diamond target
+ // std::vector<std::vector<cv::Point2f>> -> charuco_corners
// bool -> true if rvec/tvec is valid.
- // Eigen::Vector3d -> rvec
- // Eigen::Vector3d -> tvec
+ // std::vector<Eigen::Vector3d> -> rvec
+ // std::vector<Eigen::Vector3d> -> tvec
+ // NOTE: we return as a vector since all but charuco boards could have
+ // multiple targets in an image; for charuco boards, there should be just one
+ // element
CharucoExtractor(
aos::EventLoop *event_loop, std::string_view pi,
std::function<void(cv::Mat, aos::monotonic_clock::time_point,
- std::vector<int>, std::vector<cv::Point2f>, bool,
- Eigen::Vector3d, Eigen::Vector3d)> &&fn);
+ std::vector<cv::Vec4i>,
+ std::vector<std::vector<cv::Point2f>>, bool,
+ std::vector<Eigen::Vector3d>,
+ std::vector<Eigen::Vector3d>)> &&handle_charuco_fn);
+
+ // Handles the image by detecting the charuco board in it.
+ void HandleImage(cv::Mat rgb_image, aos::monotonic_clock::time_point eof);
// Returns the aruco dictionary in use.
cv::Ptr<cv::aruco::Dictionary> dictionary() const { return dictionary_; }
@@ -87,8 +101,20 @@
const cv::Mat dist_coeffs() const { return dist_coeffs_; }
private:
- // Handles the image by detecting the charuco board in it.
- void HandleImage(cv::Mat rgb_image, aos::monotonic_clock::time_point eof);
+ // Creates the dictionary, board, and other parameters for the appropriate
+ // (ch)aruco target
+ void SetupTargetData();
+
+ // Draw the axes from the pose(s) on the image
+ void DrawTargetPoses(cv::Mat rgb_image, std::vector<cv::Vec3d> rvecs,
+ std::vector<cv::Vec3d> tvecs);
+
+ // Helper function to convert rotation (rvecs) and translation (tvecs) vectors
+ // into Eigen vectors and store in corresponding vectors
+ void PackPoseResults(std::vector<cv::Vec3d> &rvecs,
+ std::vector<cv::Vec3d> &tvecs,
+ std::vector<Eigen::Vector3d> *rvecs_eigen,
+ std::vector<Eigen::Vector3d> *tvecs_eigen);
aos::EventLoop *event_loop_;
CameraCalibration calibration_;
@@ -96,18 +122,26 @@
cv::Ptr<cv::aruco::Dictionary> dictionary_;
cv::Ptr<cv::aruco::CharucoBoard> board_;
+ // Length of a side of the aruco marker
+ double marker_length_;
+ // Length of a side of the checkerboard squares (around the marker)
+ double square_length_;
+
+ // Intrinsic calibration matrix
const cv::Mat camera_matrix_;
+ // Intrinsic calibration matrix as Eigen::Matrix3d
const Eigen::Matrix3d eigen_camera_matrix_;
+ // Intrinsic distortion coefficients
const cv::Mat dist_coeffs_;
+ // Index number of the raspberry pi
const std::optional<uint16_t> pi_number_;
- ImageCallback image_callback_;
-
// Function to call.
- std::function<void(cv::Mat, aos::monotonic_clock::time_point,
- std::vector<int>, std::vector<cv::Point2f>, bool,
- Eigen::Vector3d, Eigen::Vector3d)>
+ std::function<void(
+ cv::Mat, aos::monotonic_clock::time_point, std::vector<cv::Vec4i>,
+ std::vector<std::vector<cv::Point2f>>, bool, std::vector<Eigen::Vector3d>,
+ std::vector<Eigen::Vector3d>)>
handle_charuco_;
};
diff --git a/frc971/vision/extrinsics_calibration.cc b/frc971/vision/extrinsics_calibration.cc
index a71f14d..cd6d183 100644
--- a/frc971/vision/extrinsics_calibration.cc
+++ b/frc971/vision/extrinsics_calibration.cc
@@ -6,6 +6,13 @@
#include "frc971/control_loops/runge_kutta.h"
#include "frc971/vision/calibration_accumulator.h"
#include "frc971/vision/charuco_lib.h"
+#include "frc971/vision/visualize_robot.h"
+
+#include <opencv2/core.hpp>
+#include <opencv2/core/eigen.hpp>
+#include <opencv2/highgui.hpp>
+#include <opencv2/highgui/highgui.hpp>
+#include <opencv2/imgproc.hpp>
namespace frc971 {
namespace vision {
@@ -79,17 +86,9 @@
virtual void ObserveCameraUpdate(
distributed_clock::time_point /*t*/,
Eigen::Vector3d /*board_to_camera_rotation*/,
+ Eigen::Vector3d /*board_to_camera_translation*/,
Eigen::Quaternion<Scalar> /*imu_to_world_rotation*/,
- Affine3s /*imu_to_world*/) {}
-
- void UpdateTurret(distributed_clock::time_point t,
- Eigen::Vector2d state) override {
- state_ = state;
- state_time_ = t;
- }
-
- Eigen::Vector2d state_ = Eigen::Vector2d::Zero();
- distributed_clock::time_point state_time_ = distributed_clock::min_time;
+ Affine3s /*imu_to_world*/, double /*turret_angle*/) {}
// Observes a camera measurement by applying a kalman filter correction and
// accumulating up the error associated with the step.
@@ -100,8 +99,9 @@
const double pivot_angle =
state_time_ == distributed_clock::min_time
? 0.0
- : state_(0) +
- state_(1) * chrono::duration<double>(t - state_time_).count();
+ : turret_state_(0) +
+ turret_state_(1) *
+ chrono::duration<double>(t - state_time_).count();
const Eigen::Quaternion<Scalar> board_to_camera_rotation(
frc971::controls::ToQuaternionFromRotationVector(rt.first)
@@ -143,9 +143,12 @@
H(2, 2) = static_cast<Scalar>(1.0);
const Eigen::Matrix<Scalar, 3, 1> y = z - H * x_hat_;
+ // TODO<Jim>: Need to understand dependence on this-- solutions vary by 20cm
+ // when changing from 0.01 -> 0.1
+ double obs_noise_var = ::std::pow(0.01, 2);
const Eigen::Matrix<double, 3, 3> R =
- (::Eigen::DiagonalMatrix<double, 3>().diagonal() << ::std::pow(0.01, 2),
- ::std::pow(0.01, 2), ::std::pow(0.01, 2))
+ (::Eigen::DiagonalMatrix<double, 3>().diagonal() << obs_noise_var,
+ obs_noise_var, obs_noise_var)
.finished()
.asDiagonal();
@@ -163,7 +166,8 @@
Eigen::Matrix<Scalar, 3, 1>(error.x(), error.y(), error.z()));
position_errors_.emplace_back(y);
- ObserveCameraUpdate(t, rt.first, imu_to_world_rotation, imu_to_world);
+ ObserveCameraUpdate(t, rt.first, rt.second, imu_to_world_rotation,
+ imu_to_world, pivot_angle);
}
virtual void ObserveIMUUpdate(
@@ -179,7 +183,22 @@
ObserveIMUUpdate(t, wa);
}
+ virtual void ObserveTurretUpdate(distributed_clock::time_point /*t*/,
+ Eigen::Vector2d /*turret_state*/) {}
+
+ void UpdateTurret(distributed_clock::time_point t,
+ Eigen::Vector2d state) override {
+ turret_state_ = state;
+ state_time_ = t;
+
+ ObserveTurretUpdate(t, state);
+ }
+
+ Eigen::Vector2d turret_state_ = Eigen::Vector2d::Zero();
+ distributed_clock::time_point state_time_ = distributed_clock::min_time;
+
const Eigen::Quaternion<Scalar> &orientation() const { return orientation_; }
+ const Eigen::Matrix<Scalar, 6, 1> &get_x_hat() const { return x_hat_; }
size_t num_errors() const { return errors_.size(); }
Scalar errorx(size_t i) const { return errors_[i].x(); }
@@ -373,73 +392,253 @@
vz.emplace_back(x_hat(5));
}
+ // TODO<Jim>: Could probably still do a bit more work on naming
+ // conventions and what is being shown here
frc971::analysis::Plotter plotter;
- plotter.AddFigure("position");
- plotter.AddLine(times_, rx, "x_hat(0)");
- plotter.AddLine(times_, ry, "x_hat(1)");
- plotter.AddLine(times_, rz, "x_hat(2)");
- plotter.AddLine(camera_times_, camera_x_, "Camera x");
- plotter.AddLine(camera_times_, camera_y_, "Camera y");
- plotter.AddLine(camera_times_, camera_z_, "Camera z");
- plotter.AddLine(camera_times_, camera_error_x_, "Camera error x");
- plotter.AddLine(camera_times_, camera_error_y_, "Camera error y");
- plotter.AddLine(camera_times_, camera_error_z_, "Camera error z");
+ plotter.AddFigure("bot (imu) position");
+ plotter.AddLine(times_, x, "x_hat(0)");
+ plotter.AddLine(times_, y, "x_hat(1)");
+ plotter.AddLine(times_, z, "x_hat(2)");
plotter.Publish();
- plotter.AddFigure("error");
- plotter.AddLine(times_, rx, "x_hat(0)");
- plotter.AddLine(times_, ry, "x_hat(1)");
- plotter.AddLine(times_, rz, "x_hat(2)");
- plotter.AddLine(camera_times_, camera_error_x_, "Camera error x");
- plotter.AddLine(camera_times_, camera_error_y_, "Camera error y");
- plotter.AddLine(camera_times_, camera_error_z_, "Camera error z");
+ plotter.AddFigure("bot (imu) rotation");
+ plotter.AddLine(camera_times_, imu_rot_x_, "bot (imu) rot x");
+ plotter.AddLine(camera_times_, imu_rot_y_, "bot (imu) rot y");
+ plotter.AddLine(camera_times_, imu_rot_z_, "bot (imu) rot z");
+ plotter.Publish();
+
+ plotter.AddFigure("rotation error");
+ plotter.AddLine(camera_times_, rotation_error_x_, "Error x");
+ plotter.AddLine(camera_times_, rotation_error_y_, "Error y");
+ plotter.AddLine(camera_times_, rotation_error_z_, "Error z");
+ plotter.Publish();
+
+ plotter.AddFigure("translation error");
+ plotter.AddLine(camera_times_, translation_error_x_, "Error x");
+ plotter.AddLine(camera_times_, translation_error_y_, "Error y");
+ plotter.AddLine(camera_times_, translation_error_z_, "Error z");
plotter.Publish();
plotter.AddFigure("imu");
- plotter.AddLine(camera_times_, world_gravity_x_, "world_gravity(0)");
- plotter.AddLine(camera_times_, world_gravity_y_, "world_gravity(1)");
- plotter.AddLine(camera_times_, world_gravity_z_, "world_gravity(2)");
- plotter.AddLine(imu_times_, imu_x_, "imu x");
- plotter.AddLine(imu_times_, imu_y_, "imu y");
- plotter.AddLine(imu_times_, imu_z_, "imu z");
- plotter.AddLine(times_, rx, "rotation x");
- plotter.AddLine(times_, ry, "rotation y");
- plotter.AddLine(times_, rz, "rotation z");
+ plotter.AddLine(imu_times_, imu_rate_x_, "imu gyro x");
+ plotter.AddLine(imu_times_, imu_rate_y_, "imu gyro y");
+ plotter.AddLine(imu_times_, imu_rate_z_, "imu gyro z");
+ plotter.AddLine(imu_times_, imu_accel_x_, "imu accel x");
+ plotter.AddLine(imu_times_, imu_accel_y_, "imu accel y");
+ plotter.AddLine(imu_times_, imu_accel_z_, "imu accel z");
+ plotter.AddLine(camera_times_, accel_minus_gravity_x_,
+ "accel_minus_gravity(0)");
+ plotter.AddLine(camera_times_, accel_minus_gravity_y_,
+ "accel_minus_gravity(1)");
+ plotter.AddLine(camera_times_, accel_minus_gravity_z_,
+ "accel_minus_gravity(2)");
plotter.Publish();
- plotter.AddFigure("raw");
- plotter.AddLine(imu_times_, imu_x_, "imu x");
- plotter.AddLine(imu_times_, imu_y_, "imu y");
- plotter.AddLine(imu_times_, imu_z_, "imu z");
- plotter.AddLine(imu_times_, imu_ratex_, "omega x");
- plotter.AddLine(imu_times_, imu_ratey_, "omega y");
- plotter.AddLine(imu_times_, imu_ratez_, "omega z");
- plotter.AddLine(camera_times_, raw_camera_x_, "Camera x");
- plotter.AddLine(camera_times_, raw_camera_y_, "Camera y");
- plotter.AddLine(camera_times_, raw_camera_z_, "Camera z");
+ plotter.AddFigure("raw camera observations");
+ plotter.AddLine(camera_times_, raw_camera_rot_x_, "Camera rot x");
+ plotter.AddLine(camera_times_, raw_camera_rot_y_, "Camera rot y");
+ plotter.AddLine(camera_times_, raw_camera_rot_z_, "Camera rot z");
+ plotter.AddLine(camera_times_, raw_camera_trans_x_, "Camera trans x");
+ plotter.AddLine(camera_times_, raw_camera_trans_y_, "Camera trans y");
+ plotter.AddLine(camera_times_, raw_camera_trans_z_, "Camera trans z");
plotter.Publish();
- plotter.AddFigure("xyz vel");
- plotter.AddLine(times_, x, "x");
- plotter.AddLine(times_, y, "y");
- plotter.AddLine(times_, z, "z");
+ plotter.AddFigure("xyz pos, vel estimates");
+ plotter.AddLine(times_, x, "x (x_hat(0))");
+ plotter.AddLine(times_, y, "y (x_hat(1))");
+ plotter.AddLine(times_, z, "z (x_hat(2))");
plotter.AddLine(times_, vx, "vx");
plotter.AddLine(times_, vy, "vy");
plotter.AddLine(times_, vz, "vz");
- plotter.AddLine(camera_times_, camera_position_x_, "Camera x");
- plotter.AddLine(camera_times_, camera_position_y_, "Camera y");
- plotter.AddLine(camera_times_, camera_position_z_, "Camera z");
+ plotter.AddLine(camera_times_, imu_position_x_, "x pos from board");
+ plotter.AddLine(camera_times_, imu_position_y_, "y pos from board");
+ plotter.AddLine(camera_times_, imu_position_z_, "z pos from board");
plotter.Publish();
+ // If we've got 'em, plot 'em
+ if (turret_times_.size() > 0) {
+ plotter.AddFigure("Turret angle");
+ plotter.AddLine(turret_times_, turret_angles_, "turret angle");
+ plotter.Publish();
+ }
+
plotter.Spin();
}
+ void Visualize(const CalibrationParameters &calibration_parameters) {
+ // Set up virtual camera for visualization
+ VisualizeRobot vis_robot;
+
+ // Set virtual viewing point 10 meters above the origin, rotated so the
+ // camera faces straight down
+ Eigen::Translation3d camera_trans(0, 0, 10.0);
+ Eigen::AngleAxisd camera_rot(M_PI, Eigen::Vector3d::UnitX());
+ Eigen::Affine3d camera_viewpoint = camera_trans * camera_rot;
+ vis_robot.SetViewpoint(camera_viewpoint);
+
+ // Create camera with origin in center, and focal length suitable to fit
+ // robot visualization fully in view
+ int image_width = 500;
+ double focal_length = 1000.0;
+ double intr[] = {focal_length, 0.0, image_width / 2.0,
+ 0.0, focal_length, image_width / 2.0,
+ 0.0, 0.0, 1.0};
+ cv::Mat camera_mat = cv::Mat(3, 3, CV_64FC1, intr);
+ cv::Mat dist_coeffs = cv::Mat(1, 5, CV_64F, 0.0);
+ vis_robot.SetCameraParameters(camera_mat);
+ vis_robot.SetDistortionCoefficients(dist_coeffs);
+
+ /*
+ // Draw an initial visualization
+ Eigen::Vector3d T_world_imu_vec =
+ calibration_parameters.initial_state.block<3, 1>(0, 0);
+ Eigen::Translation3d T_world_imu(T_world_imu_vec);
+ Eigen::Affine3d H_world_imu =
+ T_world_imu * calibration_parameters.initial_orientation;
+
+ vis_robot.DrawFrameAxes(H_world_imu, "imu");
+
+ Eigen::Quaterniond R_imu_pivot(calibration_parameters.pivot_to_imu);
+ Eigen::Translation3d T_imu_pivot(
+ calibration_parameters.pivot_to_imu_translation);
+ Eigen::Affine3d H_imu_pivot = T_imu_pivot * R_imu_pivot;
+ Eigen::Affine3d H_world_pivot = H_world_imu * H_imu_pivot;
+ vis_robot.DrawFrameAxes(H_world_pivot, "pivot");
+
+ Eigen::Affine3d H_imupivot_camerapivot(
+ Eigen::AngleAxisd(1.5, Eigen::Vector3d::UnitZ()));
+ Eigen::Quaterniond R_camera_pivot(calibration_parameters.pivot_to_camera);
+ Eigen::Translation3d T_camera_pivot(
+ calibration_parameters.pivot_to_camera_translation);
+ Eigen::Affine3d H_camera_pivot = T_camera_pivot * R_camera_pivot;
+ Eigen::Affine3d H_world_camera = H_world_imu * H_imu_pivot *
+ H_imupivot_camerapivot *
+ H_camera_pivot.inverse();
+ vis_robot.DrawFrameAxes(H_world_camera, "camera");
+
+ cv::imshow("Original poses", image_mat);
+ cv::waitKey();
+ */
+
+ uint current_state_index = 0;
+ uint current_turret_index = 0;
+ for (uint i = 0; i < camera_times_.size() - 1; i++) {
+ // reset image each frame
+ cv::Mat image_mat =
+ cv::Mat::zeros(cv::Size(image_width, image_width), CV_8UC3);
+ vis_robot.SetImage(image_mat);
+
+ // Jump to state closest to current camera_time
+ while (camera_times_[i] > times_[current_state_index] &&
+ current_state_index < times_.size()) {
+ current_state_index++;
+ }
+
+ // H_world_imu: map from world origin to imu (robot) frame
+ Eigen::Vector3d T_world_imu_vec =
+ x_hats_[current_state_index].block<3, 1>(0, 0);
+ Eigen::Translation3d T_world_imu(T_world_imu_vec);
+ Eigen::Affine3d H_world_imu =
+ T_world_imu * orientations_[current_state_index];
+
+ vis_robot.DrawFrameAxes(H_world_imu, "imu_kf");
+
+ // H_world_pivot: map from world origin to pivot point
+ // Do this via the imu (using H_world_pivot = H_world_imu * H_imu_pivot)
+ Eigen::Quaterniond R_imu_pivot(calibration_parameters.pivot_to_imu);
+ Eigen::Translation3d T_imu_pivot(
+ calibration_parameters.pivot_to_imu_translation);
+ Eigen::Affine3d H_imu_pivot = T_imu_pivot * R_imu_pivot;
+ Eigen::Affine3d H_world_pivot = H_world_imu * H_imu_pivot;
+ vis_robot.DrawFrameAxes(H_world_pivot, "pivot");
+
+ // Jump to turret sample closest to current camera_time
+ while (turret_times_.size() > 0 &&
+ camera_times_[i] > turret_times_[current_turret_index] &&
+ current_turret_index < turret_times_.size()) {
+ current_turret_index++;
+ }
+
+ // Draw the camera frame
+
+ Eigen::Affine3d H_imupivot_camerapivot(Eigen::Matrix4d::Identity());
+ if (turret_angles_.size() > 0) {
+ // Need to rotate by the turret angle in the middle of all this
+ H_imupivot_camerapivot = Eigen::Affine3d(Eigen::AngleAxisd(
+ turret_angles_[current_turret_index], Eigen::Vector3d::UnitZ()));
+ }
+
+ // H_world_camera: map from world origin to camera frame
+ // Via imu->pivot->pivot rotation
+ Eigen::Quaterniond R_camera_pivot(calibration_parameters.pivot_to_camera);
+ Eigen::Translation3d T_camera_pivot(
+ calibration_parameters.pivot_to_camera_translation);
+ Eigen::Affine3d H_camera_pivot = T_camera_pivot * R_camera_pivot;
+ Eigen::Affine3d H_world_camera = H_world_imu * H_imu_pivot *
+ H_imupivot_camerapivot *
+ H_camera_pivot.inverse();
+ vis_robot.DrawFrameAxes(H_world_camera, "camera");
+
+ // H_world_board: board location from world reference frame
+ // Uses the estimate from camera-> board, on top of H_world_camera
+ Eigen::Quaterniond R_camera_board(
+ frc971::controls::ToQuaternionFromRotationVector(
+ board_to_camera_rotations_[i]));
+ Eigen::Translation3d T_camera_board(board_to_camera_translations_[i]);
+ Eigen::Affine3d H_camera_board = T_camera_board * R_camera_board;
+ Eigen::Affine3d H_world_board = H_world_camera * H_camera_board;
+
+ vis_robot.DrawFrameAxes(H_world_board, "board est");
+
+ // H_world_board_solve: board in world frame based on solver
+ // Find world -> board via solved parameter of H_world_board
+ // (parameter "board_to_world" and assuming origin of board frame is
+ // coincident with origin of world frame, i.e., T_world_board == 0)
+ Eigen::Quaterniond R_world_board_solve(
+ calibration_parameters.board_to_world);
+ Eigen::Translation3d T_world_board_solve(Eigen::Vector3d(0, 0, 0));
+ Eigen::Affine3d H_world_board_solve =
+ T_world_board_solve * R_world_board_solve;
+
+ vis_robot.DrawFrameAxes(H_world_board_solve, "board_solve");
+
+ // H_world_imu_from_board: imu location in world frame, via the board
+ // Determine the imu location via the board_to_world solved
+ // transformation
+ Eigen::Affine3d H_world_imu_from_board =
+ H_world_board_solve * H_camera_board.inverse() * H_camera_pivot *
+ H_imupivot_camerapivot.inverse() * H_imu_pivot.inverse();
+
+ vis_robot.DrawFrameAxes(H_world_imu_from_board, "imu_board");
+
+ // These errors should match up with the residuals in the optimizer
+ // (Note: rotation seems to differ by sign, but that's OK in residual)
+ Eigen::Affine3d error = H_world_imu_from_board.inverse() * H_world_imu;
+ Eigen::Vector3d trans_error =
+ H_world_imu_from_board.translation() - H_world_imu.translation();
+ Eigen::Quaterniond error_rot(error.rotation());
+ VLOG(1) << "Error: \n"
+ << "Rotation: " << error_rot.coeffs().transpose() << "\n"
+ << "Translation: " << trans_error.transpose();
+
+ cv::imshow("Live", image_mat);
+ cv::waitKey(50);
+
+ if (i % 200 == 0) {
+ LOG(INFO) << "Pausing at step " << i;
+ cv::waitKey();
+ }
+ }
+ LOG(INFO) << "Finished visualizing robot. Press any key to continue";
+ cv::waitKey();
+ }
+
void ObserveIntegrated(distributed_clock::time_point t,
Eigen::Matrix<double, 6, 1> x_hat,
Eigen::Quaternion<double> orientation,
Eigen::Matrix<double, 6, 6> p) override {
- VLOG(1) << t << " -> " << p;
- VLOG(1) << t << " xhat -> " << x_hat.transpose();
+ VLOG(2) << t << " -> " << p;
+ VLOG(2) << t << " xhat -> " << x_hat.transpose();
times_.emplace_back(chrono::duration<double>(t.time_since_epoch()).count());
x_hats_.emplace_back(x_hat);
orientations_.emplace_back(orientation);
@@ -448,83 +647,126 @@
void ObserveIMUUpdate(
distributed_clock::time_point t,
std::pair<Eigen::Vector3d, Eigen::Vector3d> wa) override {
- imu_times_.emplace_back(chrono::duration<double>(t.time_since_epoch()).count());
- imu_ratex_.emplace_back(wa.first.x());
- imu_ratey_.emplace_back(wa.first.y());
- imu_ratez_.emplace_back(wa.first.z());
- imu_x_.emplace_back(wa.second.x());
- imu_y_.emplace_back(wa.second.y());
- imu_z_.emplace_back(wa.second.z());
+ imu_times_.emplace_back(
+ chrono::duration<double>(t.time_since_epoch()).count());
+ imu_rate_x_.emplace_back(wa.first.x());
+ imu_rate_y_.emplace_back(wa.first.y());
+ imu_rate_z_.emplace_back(wa.first.z());
+ imu_accel_x_.emplace_back(wa.second.x());
+ imu_accel_y_.emplace_back(wa.second.y());
+ imu_accel_z_.emplace_back(wa.second.z());
last_accel_ = wa.second;
}
void ObserveCameraUpdate(distributed_clock::time_point t,
Eigen::Vector3d board_to_camera_rotation,
+ Eigen::Vector3d board_to_camera_translation,
Eigen::Quaternion<double> imu_to_world_rotation,
- Eigen::Affine3d imu_to_world) override {
- raw_camera_x_.emplace_back(board_to_camera_rotation(0, 0));
- raw_camera_y_.emplace_back(board_to_camera_rotation(1, 0));
- raw_camera_z_.emplace_back(board_to_camera_rotation(2, 0));
+ Eigen::Affine3d imu_to_world,
+ double turret_angle) override {
+ board_to_camera_rotations_.emplace_back(board_to_camera_rotation);
+ board_to_camera_translations_.emplace_back(board_to_camera_translation);
- Eigen::Matrix<double, 3, 1> rotation_vector =
- frc971::controls::ToRotationVectorFromQuaternion(imu_to_world_rotation);
camera_times_.emplace_back(
chrono::duration<double>(t.time_since_epoch()).count());
- Eigen::Matrix<double, 3, 1> camera_error =
+ raw_camera_rot_x_.emplace_back(board_to_camera_rotation(0, 0));
+ raw_camera_rot_y_.emplace_back(board_to_camera_rotation(1, 0));
+ raw_camera_rot_z_.emplace_back(board_to_camera_rotation(2, 0));
+
+ raw_camera_trans_x_.emplace_back(board_to_camera_translation(0, 0));
+ raw_camera_trans_y_.emplace_back(board_to_camera_translation(1, 0));
+ raw_camera_trans_z_.emplace_back(board_to_camera_translation(2, 0));
+
+ Eigen::Matrix<double, 3, 1> rotation_vector =
+ frc971::controls::ToRotationVectorFromQuaternion(imu_to_world_rotation);
+ imu_rot_x_.emplace_back(rotation_vector(0, 0));
+ imu_rot_y_.emplace_back(rotation_vector(1, 0));
+ imu_rot_z_.emplace_back(rotation_vector(2, 0));
+
+ Eigen::Matrix<double, 3, 1> rotation_error =
frc971::controls::ToRotationVectorFromQuaternion(
imu_to_world_rotation.inverse() * orientation());
- camera_x_.emplace_back(rotation_vector(0, 0));
- camera_y_.emplace_back(rotation_vector(1, 0));
- camera_z_.emplace_back(rotation_vector(2, 0));
+ rotation_error_x_.emplace_back(rotation_error(0, 0));
+ rotation_error_y_.emplace_back(rotation_error(1, 0));
+ rotation_error_z_.emplace_back(rotation_error(2, 0));
- camera_error_x_.emplace_back(camera_error(0, 0));
- camera_error_y_.emplace_back(camera_error(1, 0));
- camera_error_z_.emplace_back(camera_error(2, 0));
+ Eigen::Matrix<double, 3, 1> imu_pos = get_x_hat().block<3, 1>(0, 0);
+ Eigen::Translation3d T_world_imu(imu_pos);
+ Eigen::Affine3d H_world_imu = T_world_imu * orientation();
+ Eigen::Affine3d H_error = imu_to_world.inverse() * H_world_imu;
- const Eigen::Vector3d world_gravity =
+ Eigen::Matrix<double, 3, 1> translation_error = H_error.translation();
+ translation_error_x_.emplace_back(translation_error(0, 0));
+ translation_error_y_.emplace_back(translation_error(1, 0));
+ translation_error_z_.emplace_back(translation_error(2, 0));
+
+ const Eigen::Vector3d accel_minus_gravity =
imu_to_world_rotation * last_accel_ -
Eigen::Vector3d(0, 0, kGravity) * gravity_scalar();
- const Eigen::Vector3d camera_position =
- imu_to_world * Eigen::Vector3d::Zero();
+ accel_minus_gravity_x_.emplace_back(accel_minus_gravity.x());
+ accel_minus_gravity_y_.emplace_back(accel_minus_gravity.y());
+ accel_minus_gravity_z_.emplace_back(accel_minus_gravity.z());
- world_gravity_x_.emplace_back(world_gravity.x());
- world_gravity_y_.emplace_back(world_gravity.y());
- world_gravity_z_.emplace_back(world_gravity.z());
+ const Eigen::Vector3d imu_position = imu_to_world * Eigen::Vector3d::Zero();
- camera_position_x_.emplace_back(camera_position.x());
- camera_position_y_.emplace_back(camera_position.y());
- camera_position_z_.emplace_back(camera_position.z());
+ imu_position_x_.emplace_back(imu_position.x());
+ imu_position_y_.emplace_back(imu_position.y());
+ imu_position_z_.emplace_back(imu_position.z());
+
+ turret_angles_from_camera_.emplace_back(turret_angle);
+ imu_to_world_save_.emplace_back(imu_to_world);
+ }
+
+ void ObserveTurretUpdate(distributed_clock::time_point t,
+ Eigen::Vector2d turret_state) override {
+ turret_times_.emplace_back(
+ chrono::duration<double>(t.time_since_epoch()).count());
+ turret_angles_.emplace_back(turret_state(0));
}
std::vector<double> camera_times_;
- std::vector<double> camera_x_;
- std::vector<double> camera_y_;
- std::vector<double> camera_z_;
- std::vector<double> raw_camera_x_;
- std::vector<double> raw_camera_y_;
- std::vector<double> raw_camera_z_;
- std::vector<double> camera_error_x_;
- std::vector<double> camera_error_y_;
- std::vector<double> camera_error_z_;
+ std::vector<double> imu_rot_x_;
+ std::vector<double> imu_rot_y_;
+ std::vector<double> imu_rot_z_;
+ std::vector<double> raw_camera_rot_x_;
+ std::vector<double> raw_camera_rot_y_;
+ std::vector<double> raw_camera_rot_z_;
+ std::vector<double> raw_camera_trans_x_;
+ std::vector<double> raw_camera_trans_y_;
+ std::vector<double> raw_camera_trans_z_;
+ std::vector<double> rotation_error_x_;
+ std::vector<double> rotation_error_y_;
+ std::vector<double> rotation_error_z_;
+ std::vector<double> translation_error_x_;
+ std::vector<double> translation_error_y_;
+ std::vector<double> translation_error_z_;
+ std::vector<Eigen::Vector3d> board_to_camera_rotations_;
+ std::vector<Eigen::Vector3d> board_to_camera_translations_;
- std::vector<double> world_gravity_x_;
- std::vector<double> world_gravity_y_;
- std::vector<double> world_gravity_z_;
- std::vector<double> imu_x_;
- std::vector<double> imu_y_;
- std::vector<double> imu_z_;
- std::vector<double> camera_position_x_;
- std::vector<double> camera_position_y_;
- std::vector<double> camera_position_z_;
+ std::vector<double> turret_angles_from_camera_;
+ std::vector<Eigen::Affine3d> imu_to_world_save_;
+
+ std::vector<double> imu_position_x_;
+ std::vector<double> imu_position_y_;
+ std::vector<double> imu_position_z_;
std::vector<double> imu_times_;
- std::vector<double> imu_ratex_;
- std::vector<double> imu_ratey_;
- std::vector<double> imu_ratez_;
+ std::vector<double> imu_rate_x_;
+ std::vector<double> imu_rate_y_;
+ std::vector<double> imu_rate_z_;
+ std::vector<double> accel_minus_gravity_x_;
+ std::vector<double> accel_minus_gravity_y_;
+ std::vector<double> accel_minus_gravity_z_;
+ std::vector<double> imu_accel_x_;
+ std::vector<double> imu_accel_y_;
+ std::vector<double> imu_accel_z_;
+
+ std::vector<double> turret_times_;
+ std::vector<double> turret_angles_;
std::vector<double> times_;
std::vector<Eigen::Matrix<double, 6, 1>> x_hats_;
@@ -549,6 +791,8 @@
const S *const pivot_to_imu_translation_ptr,
const S *const gravity_scalar_ptr,
const S *const accelerometer_bias_ptr, S *residual) const {
+ const aos::monotonic_clock::time_point start_time =
+ aos::monotonic_clock::now();
Eigen::Quaternion<S> initial_orientation(
initial_orientation_ptr[3], initial_orientation_ptr[0],
initial_orientation_ptr[1], initial_orientation_ptr[2]);
@@ -585,18 +829,32 @@
pivot_to_imu_translation, *gravity_scalar_ptr, accelerometer_bias);
data->ReviewData(&filter);
+ // Since the angular error scale is bounded by 1 (quaternion, so unit
+ // vector, scaled by sin(alpha)), I found it necessary to scale the
+ // angular error to have it properly balance with the translational error
+ double ang_error_scale = 5.0;
for (size_t i = 0; i < filter.num_errors(); ++i) {
- residual[3 * i + 0] = filter.errorx(i);
- residual[3 * i + 1] = filter.errory(i);
- residual[3 * i + 2] = filter.errorz(i);
+ residual[3 * i + 0] = ang_error_scale * filter.errorx(i);
+ residual[3 * i + 1] = ang_error_scale * filter.errory(i);
+ residual[3 * i + 2] = ang_error_scale * filter.errorz(i);
}
+ double trans_error_scale = 1.0;
for (size_t i = 0; i < filter.num_perrors(); ++i) {
- residual[3 * filter.num_errors() + 3 * i + 0] = filter.errorpx(i);
- residual[3 * filter.num_errors() + 3 * i + 1] = filter.errorpy(i);
- residual[3 * filter.num_errors() + 3 * i + 2] = filter.errorpz(i);
+ residual[3 * filter.num_errors() + 3 * i + 0] =
+ trans_error_scale * filter.errorpx(i);
+ residual[3 * filter.num_errors() + 3 * i + 1] =
+ trans_error_scale * filter.errorpy(i);
+ residual[3 * filter.num_errors() + 3 * i + 2] =
+ trans_error_scale * filter.errorpz(i);
}
+ LOG(INFO) << "Cost function calc took "
+ << chrono::duration<double>(aos::monotonic_clock::now() -
+ start_time)
+ .count()
+ << " seconds";
+
return true;
}
};
@@ -630,6 +888,8 @@
}
{
+ // The turret's Z rotation is redundant with the camera's mounting z
+ // rotation since it's along the rotation axis.
ceres::CostFunction *turret_z_cost_function =
new ceres::AutoDiffCostFunction<PenalizeQuaternionZ, 1, 4>(
new PenalizeQuaternionZ());
@@ -639,7 +899,8 @@
}
if (calibration_parameters->has_pivot) {
- // Constrain Z.
+ // Constrain Z since it's along the rotation axis and therefore
+ // redundant.
problem.SetParameterization(
calibration_parameters->pivot_to_imu_translation.data(),
new ceres::SubsetParameterization(3, {2}));
@@ -657,6 +918,9 @@
calibration_parameters->pivot_to_camera.coeffs().data(),
quaternion_local_parameterization);
problem.SetParameterization(
+ calibration_parameters->pivot_to_imu.coeffs().data(),
+ quaternion_local_parameterization);
+ problem.SetParameterization(
calibration_parameters->board_to_world.coeffs().data(),
quaternion_local_parameterization);
for (int i = 0; i < 3; ++i) {
@@ -678,40 +942,13 @@
ceres::Solver::Options options;
options.minimizer_progress_to_stdout = true;
options.gradient_tolerance = 1e-12;
- options.function_tolerance = 1e-16;
+ options.function_tolerance = 1e-6;
options.parameter_tolerance = 1e-12;
ceres::Solver::Summary summary;
Solve(options, &problem, &summary);
LOG(INFO) << summary.FullReport();
-
- LOG(INFO) << "initial_orientation "
- << calibration_parameters->initial_orientation.coeffs().transpose();
- LOG(INFO) << "pivot_to_imu "
- << calibration_parameters->pivot_to_imu.coeffs().transpose();
- LOG(INFO) << "pivot_to_imu(rotation) "
- << frc971::controls::ToRotationVectorFromQuaternion(
- calibration_parameters->pivot_to_imu)
- .transpose();
- LOG(INFO) << "pivot_to_camera "
- << calibration_parameters->pivot_to_camera.coeffs().transpose();
- LOG(INFO) << "pivot_to_camera(rotation) "
- << frc971::controls::ToRotationVectorFromQuaternion(
- calibration_parameters->pivot_to_camera)
- .transpose();
- LOG(INFO) << "gyro_bias " << calibration_parameters->gyro_bias.transpose();
- LOG(INFO) << "board_to_world "
- << calibration_parameters->board_to_world.coeffs().transpose();
- LOG(INFO) << "board_to_world(rotation) "
- << frc971::controls::ToRotationVectorFromQuaternion(
- calibration_parameters->board_to_world)
- .transpose();
- LOG(INFO) << "pivot_to_imu_translation "
- << calibration_parameters->pivot_to_imu_translation.transpose();
- LOG(INFO) << "pivot_to_camera_translation "
- << calibration_parameters->pivot_to_camera_translation.transpose();
- LOG(INFO) << "gravity " << kGravity * calibration_parameters->gravity_scalar;
- LOG(INFO) << "accelerometer bias "
- << calibration_parameters->accelerometer_bias.transpose();
+ LOG(INFO) << "Solution is " << (summary.IsSolutionUsable() ? "" : "NOT ")
+ << "usable";
}
void Plot(const CalibrationData &data,
@@ -730,5 +967,21 @@
filter.Plot();
}
+void Visualize(const CalibrationData &data,
+ const CalibrationParameters &calibration_parameters) {
+ PoseFilter filter(calibration_parameters.initial_orientation,
+ calibration_parameters.pivot_to_camera,
+ calibration_parameters.pivot_to_imu,
+ calibration_parameters.gyro_bias,
+ calibration_parameters.initial_state,
+ calibration_parameters.board_to_world,
+ calibration_parameters.pivot_to_camera_translation,
+ calibration_parameters.pivot_to_imu_translation,
+ calibration_parameters.gravity_scalar,
+ calibration_parameters.accelerometer_bias);
+ data.ReviewData(&filter);
+ filter.Visualize(calibration_parameters);
+}
+
} // namespace vision
} // namespace frc971
diff --git a/frc971/vision/extrinsics_calibration.h b/frc971/vision/extrinsics_calibration.h
index b24f13f..eb35482 100644
--- a/frc971/vision/extrinsics_calibration.h
+++ b/frc971/vision/extrinsics_calibration.h
@@ -42,6 +42,11 @@
void Plot(const CalibrationData &data,
const CalibrationParameters &calibration_parameters);
+// Shows the evolution of the calibration over time by visualizing relevant
+// coordinate frames in a virtual camera view
+void Visualize(const CalibrationData &data,
+ const CalibrationParameters &calibration_parameters);
+
} // namespace vision
} // namespace frc971
diff --git a/frc971/vision/target_map.fbs b/frc971/vision/target_map.fbs
new file mode 100644
index 0000000..50a9d7d
--- /dev/null
+++ b/frc971/vision/target_map.fbs
@@ -0,0 +1,27 @@
+namespace frc971.vision;
+
+// Represents 3d pose of an april tag on the field.
+table TargetPoseFbs {
+ // AprilTag ID of this target
+ id:uint64 (id: 0);
+
+ // Pose of target relative to field origin.
+ // NOTE: As of now, we only solve for the 2d pose (x, y, yaw)
+ // and all other values will be 0.
+ x:double (id: 1);
+ y:double (id: 2);
+ z:double (id: 3);
+
+ roll:double (id: 4);
+ pitch:double (id: 5);
+ yaw:double (id: 6);
+}
+
+// Map of all target poses on a field.
+// This would be solved for by TargetMapper
+table TargetMap {
+ target_poses:[TargetPoseFbs] (id: 0);
+
+ // Unique name of the field
+ field_name:string (id: 1);
+}
\ No newline at end of file
diff --git a/frc971/vision/target_mapper.cc b/frc971/vision/target_mapper.cc
new file mode 100644
index 0000000..ccaa805
--- /dev/null
+++ b/frc971/vision/target_mapper.cc
@@ -0,0 +1,362 @@
+#include "frc971/vision/target_mapper.h"
+
+#include "frc971/control_loops/control_loop.h"
+#include "frc971/vision/ceres/angle_local_parameterization.h"
+#include "frc971/vision/ceres/normalize_angle.h"
+#include "frc971/vision/ceres/pose_graph_2d_error_term.h"
+#include "frc971/vision/geometry.h"
+
+DEFINE_uint64(max_num_iterations, 100,
+ "Maximum number of iterations for the ceres solver");
+
+namespace frc971::vision {
+
+Eigen::Affine3d PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d pose2d) {
+ Eigen::Affine3d H_world_pose =
+ Eigen::Translation3d(pose2d.x, pose2d.y, 0.0) *
+ Eigen::AngleAxisd(pose2d.yaw_radians, Eigen::Vector3d::UnitZ());
+ return H_world_pose;
+}
+
+ceres::examples::Pose2d PoseUtils::Affine3dToPose2d(Eigen::Affine3d H) {
+ Eigen::Vector3d T = H.translation();
+ double heading = std::atan2(H.rotation()(1, 0), H.rotation()(0, 0));
+ return ceres::examples::Pose2d{T[0], T[1],
+ ceres::examples::NormalizeAngle(heading)};
+}
+
+ceres::examples::Pose2d PoseUtils::ComputeRelativePose(
+ ceres::examples::Pose2d pose_1, ceres::examples::Pose2d pose_2) {
+ Eigen::Affine3d H_world_1 = Pose2dToAffine3d(pose_1);
+ Eigen::Affine3d H_world_2 = Pose2dToAffine3d(pose_2);
+
+ // Get the location of 2 in the 1 frame
+ Eigen::Affine3d H_1_2 = H_world_1.inverse() * H_world_2;
+ return Affine3dToPose2d(H_1_2);
+}
+
+ceres::examples::Pose2d PoseUtils::ComputeOffsetPose(
+ ceres::examples::Pose2d pose_1, ceres::examples::Pose2d pose_2_relative) {
+ auto H_world_1 = Pose2dToAffine3d(pose_1);
+ auto H_1_2 = Pose2dToAffine3d(pose_2_relative);
+ auto H_world_2 = H_world_1 * H_1_2;
+
+ return Affine3dToPose2d(H_world_2);
+}
+
+namespace {
+double ExponentiatedSinTerm(double theta) {
+ return (theta == 0.0 ? 1.0 : std::sin(theta) / theta);
+}
+
+double ExponentiatedCosTerm(double theta) {
+ return (theta == 0.0 ? 0.0 : (1 - std::cos(theta)) / theta);
+}
+} // namespace
+
+ceres::examples::Pose2d DataAdapter::InterpolatePose(
+ const TimestampedPose &pose_start, const TimestampedPose &pose_end,
+ aos::distributed_clock::time_point time) {
+ auto delta_pose =
+ PoseUtils::ComputeRelativePose(pose_start.pose, pose_end.pose);
+ // Time from start of period, on the scale 0-1 where 1 is the end.
+ double interpolation_scalar =
+ static_cast<double>((time - pose_start.time).count()) /
+ static_cast<double>((pose_end.time - pose_start.time).count());
+
+ double theta = delta_pose.yaw_radians;
+ // Take the log of the transformation matrix:
+ // https://mathoverflow.net/questions/118533/how-to-compute-se2-group-exponential-and-logarithm
+ StdFormLine dx_line = {.a = ExponentiatedSinTerm(theta),
+ .b = -ExponentiatedCosTerm(theta),
+ .c = delta_pose.x};
+ StdFormLine dy_line = {.a = ExponentiatedCosTerm(theta),
+ .b = ExponentiatedSinTerm(theta),
+ .c = delta_pose.y};
+
+ std::optional<cv::Point2d> solution = dx_line.Intersection(dy_line);
+ CHECK(solution.has_value());
+
+ // Re-exponentiate with the new values scaled by the interpolation scalar to
+ // get an interpolated tranformation matrix
+ double a = solution->x * interpolation_scalar;
+ double b = solution->y * interpolation_scalar;
+ double alpha = theta * interpolation_scalar;
+
+ ceres::examples::Pose2d interpolated_pose = {
+ .x = a * ExponentiatedSinTerm(theta) - b * ExponentiatedCosTerm(theta),
+ .y = a * ExponentiatedCosTerm(theta) + b * ExponentiatedSinTerm(theta),
+ .yaw_radians = alpha};
+
+ return PoseUtils::ComputeOffsetPose(pose_start.pose, interpolated_pose);
+} // namespace frc971::vision
+
+std::pair<std::vector<ceres::examples::Constraint2d>,
+ std::vector<ceres::examples::Pose2d>>
+DataAdapter::MatchTargetDetections(
+ const std::vector<TimestampedPose> ×tamped_robot_poses,
+ const std::vector<TimestampedDetection> ×tamped_target_detections) {
+ // Interpolate robot poses
+ std::map<aos::distributed_clock::time_point, ceres::examples::Pose2d>
+ interpolated_poses;
+
+ CHECK_GT(timestamped_robot_poses.size(), 1ul)
+ << "Need more than 1 robot pose";
+ auto robot_pose_it = timestamped_robot_poses.begin();
+ for (const auto ×tamped_detection : timestamped_target_detections) {
+ aos::distributed_clock::time_point target_time = timestamped_detection.time;
+ // Find the robot point right before this localization
+ while (robot_pose_it->time > target_time ||
+ (robot_pose_it + 1)->time <= target_time) {
+ robot_pose_it++;
+ CHECK(robot_pose_it < timestamped_robot_poses.end() - 1)
+ << "Need a robot pose before and after every target detection";
+ }
+
+ auto start = robot_pose_it;
+ auto end = robot_pose_it + 1;
+ interpolated_poses.emplace(target_time,
+ InterpolatePose(*start, *end, target_time));
+ }
+
+ // Match consecutive detections
+ std::vector<ceres::examples::Constraint2d> target_constraints;
+ std::vector<ceres::examples::Pose2d> robot_delta_poses;
+
+ auto last_detection = timestamped_target_detections[0];
+ auto last_robot_pose =
+ interpolated_poses[timestamped_target_detections[0].time];
+
+ for (auto it = timestamped_target_detections.begin() + 1;
+ it < timestamped_target_detections.end(); it++) {
+ // Skip two consecutive detections of the same target, because the solver
+ // doesn't allow this
+ if (it->id == last_detection.id) {
+ continue;
+ }
+
+ auto robot_pose = interpolated_poses[it->time];
+ auto robot_delta_pose =
+ PoseUtils::ComputeRelativePose(last_robot_pose, robot_pose);
+ auto confidence = ComputeConfidence(last_detection.time, it->time);
+
+ target_constraints.emplace_back(ComputeTargetConstraint(
+ last_detection, PoseUtils::Pose2dToAffine3d(robot_delta_pose), *it,
+ confidence));
+ robot_delta_poses.emplace_back(robot_delta_pose);
+
+ last_detection = *it;
+ last_robot_pose = robot_pose;
+ }
+
+ return {target_constraints, robot_delta_poses};
+}
+
+Eigen::Matrix3d DataAdapter::ComputeConfidence(
+ aos::distributed_clock::time_point start,
+ aos::distributed_clock::time_point end) {
+ constexpr size_t kX = 0;
+ constexpr size_t kY = 1;
+ constexpr size_t kTheta = 2;
+
+ // Uncertainty matrix between start and end
+ Eigen::Matrix3d P = Eigen::Matrix3d::Zero();
+
+ {
+ // Noise for odometry-based robot position measurements
+ Eigen::Matrix3d Q_odometry = Eigen::Matrix3d::Zero();
+ Q_odometry(kX, kX) = std::pow(0.045, 2);
+ Q_odometry(kY, kY) = std::pow(0.045, 2);
+ Q_odometry(kTheta, kTheta) = std::pow(0.01, 2);
+
+ // Add uncertainty for robot position measurements from start to end
+ int iterations = (end - start) / frc971::controls::kLoopFrequency;
+ P += static_cast<double>(iterations) * Q_odometry;
+ }
+
+ {
+ // Noise for vision-based target localizations
+ Eigen::Matrix3d Q_vision = Eigen::Matrix3d::Zero();
+ Q_vision(kX, kX) = std::pow(0.09, 2);
+ Q_vision(kY, kY) = std::pow(0.09, 2);
+ Q_vision(kTheta, kTheta) = std::pow(0.02, 2);
+
+ // Add uncertainty for the 2 vision measurements (1 at start and 1 at end)
+ P += 2.0 * Q_vision;
+ }
+
+ return P.inverse();
+}
+
+ceres::examples::Constraint2d DataAdapter::ComputeTargetConstraint(
+ const TimestampedDetection &target_detection_start,
+ const Eigen::Affine3d &H_robotstart_robotend,
+ const TimestampedDetection &target_detection_end,
+ const Eigen::Matrix3d &confidence) {
+ // Compute the relative pose (constraint) between the two targets
+ Eigen::Affine3d H_targetstart_targetend =
+ target_detection_start.H_robot_target.inverse() * H_robotstart_robotend *
+ target_detection_end.H_robot_target;
+ ceres::examples::Pose2d target_constraint =
+ PoseUtils::Affine3dToPose2d(H_targetstart_targetend);
+
+ return ceres::examples::Constraint2d{
+ target_detection_start.id, target_detection_end.id,
+ target_constraint.x, target_constraint.y,
+ target_constraint.yaw_radians, confidence};
+}
+
+TargetMapper::TargetMapper(
+ std::string_view target_poses_path,
+ std::vector<ceres::examples::Constraint2d> target_constraints)
+ : target_constraints_(target_constraints) {
+ aos::FlatbufferDetachedBuffer<TargetMap> target_map =
+ aos::JsonFileToFlatbuffer<TargetMap>(target_poses_path);
+ for (const auto *target_pose_fbs : *target_map.message().target_poses()) {
+ target_poses_[target_pose_fbs->id()] = ceres::examples::Pose2d{
+ target_pose_fbs->x(), target_pose_fbs->y(), target_pose_fbs->yaw()};
+ }
+}
+
+TargetMapper::TargetMapper(
+ std::map<TargetId, ceres::examples::Pose2d> target_poses,
+ std::vector<ceres::examples::Constraint2d> target_constraints)
+ : target_poses_(target_poses), target_constraints_(target_constraints) {}
+
+std::optional<TargetMapper::TargetPose> TargetMapper::GetTargetPoseById(
+ std::vector<TargetMapper::TargetPose> target_poses, TargetId target_id) {
+ for (auto target_pose : target_poses) {
+ if (target_pose.id == target_id) {
+ return target_pose;
+ }
+ }
+
+ return std::nullopt;
+}
+
+// Taken from ceres/examples/slam/pose_graph_2d/pose_graph_2d.cc
+void TargetMapper::BuildOptimizationProblem(
+ std::map<int, ceres::examples::Pose2d> *poses,
+ const std::vector<ceres::examples::Constraint2d> &constraints,
+ ceres::Problem *problem) {
+ CHECK_NOTNULL(poses);
+ CHECK_NOTNULL(problem);
+ if (constraints.empty()) {
+ LOG(WARNING) << "No constraints, no problem to optimize.";
+ return;
+ }
+
+ ceres::LossFunction *loss_function = new ceres::HuberLoss(2.0);
+ ceres::LocalParameterization *angle_local_parameterization =
+ ceres::examples::AngleLocalParameterization::Create();
+
+ for (std::vector<ceres::examples::Constraint2d>::const_iterator
+ constraints_iter = constraints.begin();
+ constraints_iter != constraints.end(); ++constraints_iter) {
+ const ceres::examples::Constraint2d &constraint = *constraints_iter;
+
+ std::map<int, ceres::examples::Pose2d>::iterator pose_begin_iter =
+ poses->find(constraint.id_begin);
+ CHECK(pose_begin_iter != poses->end())
+ << "Pose with ID: " << constraint.id_begin << " not found.";
+ std::map<int, ceres::examples::Pose2d>::iterator pose_end_iter =
+ poses->find(constraint.id_end);
+ CHECK(pose_end_iter != poses->end())
+ << "Pose with ID: " << constraint.id_end << " not found.";
+
+ const Eigen::Matrix3d sqrt_information =
+ constraint.information.llt().matrixL();
+ // Ceres will take ownership of the pointer.
+ ceres::CostFunction *cost_function =
+ ceres::examples::PoseGraph2dErrorTerm::Create(
+ constraint.x, constraint.y, constraint.yaw_radians,
+ sqrt_information);
+ problem->AddResidualBlock(
+ cost_function, loss_function, &pose_begin_iter->second.x,
+ &pose_begin_iter->second.y, &pose_begin_iter->second.yaw_radians,
+ &pose_end_iter->second.x, &pose_end_iter->second.y,
+ &pose_end_iter->second.yaw_radians);
+
+ problem->SetParameterization(&pose_begin_iter->second.yaw_radians,
+ angle_local_parameterization);
+ problem->SetParameterization(&pose_end_iter->second.yaw_radians,
+ angle_local_parameterization);
+ }
+
+ // The pose graph optimization problem has three DOFs that are not fully
+ // constrained. This is typically referred to as gauge freedom. You can apply
+ // a rigid body transformation to all the nodes and the optimization problem
+ // will still have the exact same cost. The Levenberg-Marquardt algorithm has
+ // internal damping which mitigates this issue, but it is better to properly
+ // constrain the gauge freedom. This can be done by setting one of the poses
+ // as constant so the optimizer cannot change it.
+ std::map<int, ceres::examples::Pose2d>::iterator pose_start_iter =
+ poses->begin();
+ CHECK(pose_start_iter != poses->end()) << "There are no poses.";
+ problem->SetParameterBlockConstant(&pose_start_iter->second.x);
+ problem->SetParameterBlockConstant(&pose_start_iter->second.y);
+ problem->SetParameterBlockConstant(&pose_start_iter->second.yaw_radians);
+}
+
+// Taken from ceres/examples/slam/pose_graph_2d/pose_graph_2d.cc
+bool TargetMapper::SolveOptimizationProblem(ceres::Problem *problem) {
+ CHECK_NOTNULL(problem);
+
+ ceres::Solver::Options options;
+ options.max_num_iterations = FLAGS_max_num_iterations;
+ options.linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY;
+
+ ceres::Solver::Summary summary;
+ ceres::Solve(options, problem, &summary);
+
+ LOG(INFO) << summary.FullReport() << '\n';
+
+ return summary.IsSolutionUsable();
+}
+
+void TargetMapper::Solve(std::string_view field_name,
+ std::optional<std::string_view> output_dir) {
+ ceres::Problem problem;
+ BuildOptimizationProblem(&target_poses_, target_constraints_, &problem);
+
+ CHECK(SolveOptimizationProblem(&problem))
+ << "The solve was not successful, exiting.";
+
+ // TODO(milind): add origin to first target offset to all poses
+
+ auto map_json = MapToJson(field_name);
+ VLOG(1) << "Solved target poses: " << map_json;
+
+ if (output_dir.has_value()) {
+ std::string output_path =
+ absl::StrCat(output_dir.value(), "/", field_name, ".json");
+ LOG(INFO) << "Writing map to file: " << output_path;
+ aos::util::WriteStringToFileOrDie(output_path, map_json);
+ }
+}
+
+std::string TargetMapper::MapToJson(std::string_view field_name) const {
+ flatbuffers::FlatBufferBuilder fbb;
+
+ // Convert poses to flatbuffers
+ std::vector<flatbuffers::Offset<TargetPoseFbs>> target_poses_fbs;
+ for (const auto &[id, pose] : target_poses_) {
+ TargetPoseFbs::Builder target_pose_builder(fbb);
+ target_pose_builder.add_id(id);
+ target_pose_builder.add_x(id);
+ target_pose_builder.add_y(id);
+ target_pose_builder.add_yaw(id);
+
+ target_poses_fbs.emplace_back(target_pose_builder.Finish());
+ }
+
+ const auto field_name_offset = fbb.CreateString(field_name);
+ flatbuffers::Offset<TargetMap> target_map_offset = CreateTargetMap(
+ fbb, fbb.CreateVector(target_poses_fbs), field_name_offset);
+
+ return aos::FlatbufferToJson(
+ flatbuffers::GetMutableTemporaryPointer(fbb, target_map_offset),
+ {.multi_line = true});
+}
+
+} // namespace frc971::vision
diff --git a/frc971/vision/target_mapper.h b/frc971/vision/target_mapper.h
new file mode 100644
index 0000000..4dbe52b
--- /dev/null
+++ b/frc971/vision/target_mapper.h
@@ -0,0 +1,144 @@
+#ifndef FRC971_VISION_TARGET_MAPPER_H_
+#define FRC971_VISION_TARGET_MAPPER_H_
+
+#include <unordered_map>
+
+#include "aos/events/simulated_event_loop.h"
+#include "frc971/vision/ceres/types.h"
+#include "frc971/vision/target_map_generated.h"
+
+namespace frc971::vision {
+
+// Estimates positions of vision targets (ex. April Tags) using
+// target detections relative to a robot (which were computed using robot
+// positions at the time of those detections). Solves SLAM problem to estimate
+// target locations using deltas between consecutive target detections.
+class TargetMapper {
+ public:
+ using TargetId = int;
+
+ struct TargetPose {
+ TargetId id;
+ // TOOD(milind): switch everything to 3d once we're more confident in 2d
+ // solving
+ ceres::examples::Pose2d pose;
+ };
+
+ // target_poses_path is the path to a TargetMap json with initial guesses for
+ // the actual locations of the targets on the field.
+ // target_constraints are the deltas between consecutive target detections,
+ // and are usually prepared by the DataAdapter class below.
+ TargetMapper(std::string_view target_poses_path,
+ std::vector<ceres::examples::Constraint2d> target_constraints);
+ // Alternate constructor for tests.
+ // Takes in the actual intial guesses instead of a file containing them
+ TargetMapper(std::map<TargetId, ceres::examples::Pose2d> target_poses,
+ std::vector<ceres::examples::Constraint2d> target_constraints);
+
+ // Solves for the target map. If output_dir is set, the map will be saved to
+ // output_dir/field_name.json
+ void Solve(std::string_view field_name,
+ std::optional<std::string_view> output_dir = std::nullopt);
+
+ // Prints target poses into a TargetMap flatbuffer json
+ std::string MapToJson(std::string_view field_name) const;
+
+ static std::optional<TargetPose> GetTargetPoseById(
+ std::vector<TargetPose> target_poses, TargetId target_id);
+
+ std::map<TargetId, ceres::examples::Pose2d> target_poses() {
+ return target_poses_;
+ }
+
+ private:
+ // Constructs the nonlinear least squares optimization problem from the
+ // pose graph constraints.
+ void BuildOptimizationProblem(
+ std::map<TargetId, ceres::examples::Pose2d> *target_poses,
+ const std::vector<ceres::examples::Constraint2d> &constraints,
+ ceres::Problem *problem);
+
+ // Returns true if the solve was successful.
+ bool SolveOptimizationProblem(ceres::Problem *problem);
+
+ std::map<TargetId, ceres::examples::Pose2d> target_poses_;
+ std::vector<ceres::examples::Constraint2d> target_constraints_;
+};
+
+// Utility functions for dealing with ceres::examples::Pose2d structs
+class PoseUtils {
+ public:
+ // Embeds a 2d pose into a 3d affine transformation to be used in 3d
+ // computation
+ static Eigen::Affine3d Pose2dToAffine3d(ceres::examples::Pose2d pose2d);
+ // Assumes only x and y translation, and only z rotation (yaw)
+ static ceres::examples::Pose2d Affine3dToPose2d(Eigen::Affine3d H);
+
+ // Computes pose_2 relative to pose_1. This is equivalent to (pose_1^-1 *
+ // pose_2)
+ static ceres::examples::Pose2d ComputeRelativePose(
+ ceres::examples::Pose2d pose_1, ceres::examples::Pose2d pose_2);
+
+ // Computes pose_2 given a pose_1 and pose_2 relative to pose_1. This is
+ // equivalent to (pose_1 * pose_2_relative)
+ static ceres::examples::Pose2d ComputeOffsetPose(
+ ceres::examples::Pose2d pose_1, ceres::examples::Pose2d pose_2_relative);
+};
+
+// Transforms robot position and target detection data into target constraints
+// to be used for mapping. Interpolates continous-time data, converting it to
+// discrete detection time steps.
+class DataAdapter {
+ public:
+ // Pairs pose with a time point
+ struct TimestampedPose {
+ aos::distributed_clock::time_point time;
+ ceres::examples::Pose2d pose;
+ };
+
+ // Pairs target detection with a time point
+ struct TimestampedDetection {
+ aos::distributed_clock::time_point time;
+ // Pose of target relative to robot
+ Eigen::Affine3d H_robot_target;
+ TargetMapper::TargetId id;
+ };
+
+ // Pairs consecutive target detections into constraints, and interpolates
+ // robot poses based on time points to compute motion between detections. This
+ // prepares data to be used by TargetMapper. Also returns vector of delta
+ // robot poses corresponding to each constraint, to be used for testing.
+ //
+ // Assumes both inputs are in chronological order.
+ static std::pair<std::vector<ceres::examples::Constraint2d>,
+ std::vector<ceres::examples::Pose2d>>
+ MatchTargetDetections(
+ const std::vector<TimestampedPose> ×tamped_robot_poses,
+ const std::vector<TimestampedDetection> ×tamped_target_detections);
+
+ // Computes inverse of covariance matrix, assuming there was a target
+ // detection between robot movement over the given time period. Ceres calls
+ // this matrix the "information"
+ static Eigen::Matrix3d ComputeConfidence(
+ aos::distributed_clock::time_point start,
+ aos::distributed_clock::time_point end);
+
+ private:
+ static ceres::examples::Pose2d InterpolatePose(
+ const TimestampedPose &pose_start, const TimestampedPose &pose_end,
+ aos::distributed_clock::time_point time);
+
+ // Computes the constraint between the start and end pose of the targets: the
+ // relative pose between the start and end target locations in the frame of
+ // the start target. Takes into account the robot motion in the time between
+ // the two detections.
+ static ceres::examples::Constraint2d ComputeTargetConstraint(
+ const TimestampedDetection &target_detection_start,
+ const Eigen::Affine3d &H_robotstart_robotend,
+ const TimestampedDetection &target_detection_end,
+ const Eigen::Matrix3d &confidence);
+};
+
+} // namespace frc971::vision
+
+#endif // FRC971_VISION_TARGET_MAPPER_H_
diff --git a/frc971/vision/target_mapper_test.cc b/frc971/vision/target_mapper_test.cc
new file mode 100644
index 0000000..f56cd8d
--- /dev/null
+++ b/frc971/vision/target_mapper_test.cc
@@ -0,0 +1,414 @@
+#include "frc971/vision/target_mapper.h"
+
+#include <random>
+
+#include "aos/events/simulated_event_loop.h"
+#include "aos/testing/random_seed.h"
+#include "glog/logging.h"
+#include "gtest/gtest.h"
+
+namespace frc971::vision {
+
+namespace {
+constexpr double kToleranceMeters = 0.05;
+constexpr double kToleranceRadians = 0.05;
+constexpr std::string_view kFieldName = "test";
+} // namespace
+
+#define EXPECT_POSE_NEAR(pose1, pose2) \
+ EXPECT_NEAR(pose1.x, pose2.x, kToleranceMeters); \
+ EXPECT_NEAR(pose1.y, pose2.y, kToleranceMeters); \
+ EXPECT_NEAR(pose1.yaw_radians, pose2.yaw_radians, kToleranceRadians);
+
+#define EXPECT_POSE_EQ(pose1, pose2) \
+ EXPECT_DOUBLE_EQ(pose1.x, pose2.x); \
+ EXPECT_DOUBLE_EQ(pose1.y, pose2.y); \
+ EXPECT_DOUBLE_EQ(pose1.yaw_radians, pose2.yaw_radians);
+
+#define EXPECT_BETWEEN_EXCLUSIVE(value, a, b) \
+ { \
+ auto low = std::min(a, b); \
+ auto high = std::max(a, b); \
+ EXPECT_GT(value, low); \
+ EXPECT_LT(value, high); \
+ }
+
+namespace {
+// Expects angles to be normalized
+double DeltaAngle(double a, double b) {
+ double delta = std::abs(a - b);
+ return std::min(delta, (2.0 * M_PI) - delta);
+}
+} // namespace
+
+// Expects angles to be normalized
+#define EXPECT_ANGLE_BETWEEN_EXCLUSIVE(theta, a, b) \
+ EXPECT_LT(DeltaAngle(a, theta), DeltaAngle(a, b)); \
+ EXPECT_LT(DeltaAngle(b, theta), DeltaAngle(a, b));
+
+#define EXPECT_POSE_IN_RANGE(interpolated_pose, pose_start, pose_end) \
+ EXPECT_BETWEEN_EXCLUSIVE(interpolated_pose.x, pose_start.x, pose_end.x); \
+ EXPECT_BETWEEN_EXCLUSIVE(interpolated_pose.y, pose_start.y, pose_end.y); \
+ EXPECT_ANGLE_BETWEEN_EXCLUSIVE(interpolated_pose.yaw_radians, \
+ pose_start.yaw_radians, \
+ pose_end.yaw_radians);
+
+// Both confidence matrixes should have the same dimensions and be square
+#define EXPECT_CONFIDENCE_GT(confidence1, confidence2) \
+ { \
+ ASSERT_EQ(confidence1.rows(), confidence2.rows()); \
+ ASSERT_EQ(confidence1.rows(), confidence1.cols()); \
+ ASSERT_EQ(confidence2.rows(), confidence2.cols()); \
+ for (size_t i = 0; i < confidence1.rows(); i++) { \
+ EXPECT_GT(confidence1(i, i), confidence2(i, i)); \
+ } \
+ }
+
+namespace {
+ceres::examples::Pose2d MakePose(double x, double y, double yaw_radians) {
+ return ceres::examples::Pose2d{x, y, yaw_radians};
+}
+
+bool TargetIsInView(TargetMapper::TargetPose target_detection) {
+ // And check if it is within the fov of the robot /
+ // camera, assuming camera is pointing in the
+ // positive x-direction of the robot
+ double angle_to_target =
+ atan2(target_detection.pose.y, target_detection.pose.x);
+
+ // Simulated camera field of view, in radians
+ constexpr double kCameraFov = M_PI_2;
+ if (fabs(angle_to_target) <= kCameraFov / 2.0) {
+ VLOG(2) << "Found target in view, based on T = " << target_detection.pose.x
+ << ", " << target_detection.pose.y << " with angle "
+ << angle_to_target;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+aos::distributed_clock::time_point TimeInMs(size_t ms) {
+ return aos::distributed_clock::time_point(std::chrono::milliseconds(ms));
+}
+
+} // namespace
+
+TEST(DataAdapterTest, Interpolation) {
+ std::vector<DataAdapter::TimestampedPose> timestamped_robot_poses = {
+ {TimeInMs(0), ceres::examples::Pose2d{1.0, 2.0, 0.0}},
+ {TimeInMs(5), ceres::examples::Pose2d{1.0, 2.0, 0.0}},
+ {TimeInMs(10), ceres::examples::Pose2d{3.0, 1.0, M_PI_2}},
+ {TimeInMs(15), ceres::examples::Pose2d{5.0, -2.0, -M_PI}},
+ {TimeInMs(20), ceres::examples::Pose2d{5.0, -2.0, -M_PI}},
+ {TimeInMs(25), ceres::examples::Pose2d{10.0, -32.0, M_PI_2}},
+ {TimeInMs(30), ceres::examples::Pose2d{-15.0, 12.0, 0.0}},
+ {TimeInMs(35), ceres::examples::Pose2d{-15.0, 12.0, 0.0}}};
+ std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections =
+ {{TimeInMs(5),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{5.0, -4.0, 0.0}),
+ 0},
+ {TimeInMs(9),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{5.0, -4.0, 0.0}),
+ 1},
+ {TimeInMs(9),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{5.0, -4.0, 0.0}),
+ 2},
+ {TimeInMs(15),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{5.0, -4.0, 0.0}),
+ 0},
+ {TimeInMs(16),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{5.0, -4.0, 0.0}),
+ 2},
+ {TimeInMs(27),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{5.0, -4.0, 0.0}),
+ 1}};
+ auto [target_constraints, robot_delta_poses] =
+ DataAdapter::MatchTargetDetections(timestamped_robot_poses,
+ timestamped_target_detections);
+
+ // Check that target constraints got inserted in the
+ // correct spots
+ EXPECT_EQ(target_constraints.size(),
+ timestamped_target_detections.size() - 1);
+ for (auto it = target_constraints.begin(); it < target_constraints.end();
+ it++) {
+ auto timestamped_it = timestamped_target_detections.begin() +
+ (it - target_constraints.begin());
+ EXPECT_EQ(it->id_begin, timestamped_it->id);
+ EXPECT_EQ(it->id_end, (timestamped_it + 1)->id);
+ }
+
+ // Check that poses were interpolated correctly.
+ // Keep track of the computed robot pose by adding the delta poses
+ auto computed_robot_pose = timestamped_robot_poses[1].pose;
+
+ computed_robot_pose =
+ PoseUtils::ComputeOffsetPose(computed_robot_pose, robot_delta_poses[0]);
+ EXPECT_POSE_IN_RANGE(computed_robot_pose, timestamped_robot_poses[1].pose,
+ timestamped_robot_poses[2].pose);
+
+ computed_robot_pose =
+ PoseUtils::ComputeOffsetPose(computed_robot_pose, robot_delta_poses[1]);
+ EXPECT_POSE_IN_RANGE(computed_robot_pose, timestamped_robot_poses[1].pose,
+ timestamped_robot_poses[2].pose);
+ EXPECT_POSE_EQ(robot_delta_poses[1], MakePose(0.0, 0.0, 0.0));
+
+ computed_robot_pose =
+ PoseUtils::ComputeOffsetPose(computed_robot_pose, robot_delta_poses[2]);
+ EXPECT_POSE_EQ(computed_robot_pose, timestamped_robot_poses[3].pose);
+
+ computed_robot_pose =
+ PoseUtils::ComputeOffsetPose(computed_robot_pose, robot_delta_poses[3]);
+ EXPECT_POSE_EQ(computed_robot_pose, timestamped_robot_poses[4].pose);
+
+ computed_robot_pose =
+ PoseUtils::ComputeOffsetPose(computed_robot_pose, robot_delta_poses[4]);
+ EXPECT_POSE_IN_RANGE(computed_robot_pose, timestamped_robot_poses[5].pose,
+ timestamped_robot_poses[6].pose);
+
+ // Check the confidence matrices. Don't check the actual values
+ // in case the constants change, just check the confidence of contraints
+ // relative to each other, as constraints over longer time periods should have
+ // lower confidence.
+ const auto confidence_0ms =
+ DataAdapter::ComputeConfidence(TimeInMs(0), TimeInMs(0));
+ const auto confidence_1ms =
+ DataAdapter::ComputeConfidence(TimeInMs(0), TimeInMs(1));
+ const auto confidence_4ms =
+ DataAdapter::ComputeConfidence(TimeInMs(0), TimeInMs(4));
+ const auto confidence_6ms =
+ DataAdapter::ComputeConfidence(TimeInMs(0), TimeInMs(6));
+ const auto confidence_11ms =
+ DataAdapter::ComputeConfidence(TimeInMs(0), TimeInMs(11));
+
+ // Check relative magnitude of different confidences.
+ // Confidences for 0-5ms, 5-10ms, and 10-15ms periods are equal
+ // because they fit within one control loop iteration.
+ EXPECT_EQ(confidence_0ms, confidence_1ms);
+ EXPECT_EQ(confidence_1ms, confidence_4ms);
+ EXPECT_CONFIDENCE_GT(confidence_4ms, confidence_6ms);
+ EXPECT_CONFIDENCE_GT(confidence_6ms, confidence_11ms);
+
+ // Check that confidences (information) of actual constraints are correct
+ EXPECT_EQ(target_constraints[0].information, confidence_4ms);
+ EXPECT_EQ(target_constraints[1].information, confidence_0ms);
+ EXPECT_EQ(target_constraints[2].information, confidence_6ms);
+ EXPECT_EQ(target_constraints[3].information, confidence_1ms);
+ EXPECT_EQ(target_constraints[4].information, confidence_11ms);
+}
+
+TEST(TargetMapperTest, TwoTargetsOneConstraint) {
+ std::map<TargetMapper::TargetId, ceres::examples::Pose2d> target_poses;
+ target_poses[0] = ceres::examples::Pose2d{5.0, 0.0, M_PI};
+ target_poses[1] = ceres::examples::Pose2d{-5.0, 0.0, 0.0};
+
+ std::vector<DataAdapter::TimestampedPose> timestamped_robot_poses = {
+ {TimeInMs(5), ceres::examples::Pose2d{2.0, 0.0, 0.0}},
+ {TimeInMs(10), ceres::examples::Pose2d{-1.0, 0.0, 0.0}},
+ {TimeInMs(15), ceres::examples::Pose2d{-1.0, 0.0, 0.0}}};
+ std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections =
+ {{TimeInMs(5),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{3.0, 0.0, M_PI}),
+ 0},
+ {TimeInMs(10),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{-4.0, 0.0, 0.0}),
+ 1}};
+ auto target_constraints =
+ DataAdapter::MatchTargetDetections(timestamped_robot_poses,
+ timestamped_target_detections)
+ .first;
+
+ frc971::vision::TargetMapper mapper(target_poses, target_constraints);
+ mapper.Solve(kFieldName);
+
+ ASSERT_EQ(mapper.target_poses().size(), 2);
+ EXPECT_POSE_NEAR(mapper.target_poses()[0], MakePose(5.0, 0.0, M_PI));
+ EXPECT_POSE_NEAR(mapper.target_poses()[1], MakePose(-5.0, 0.0, 0.0));
+}
+
+TEST(TargetMapperTest, TwoTargetsTwoConstraints) {
+ std::map<TargetMapper::TargetId, ceres::examples::Pose2d> target_poses;
+ target_poses[0] = ceres::examples::Pose2d{5.0, 0.0, M_PI};
+ target_poses[1] = ceres::examples::Pose2d{-5.0, 0.0, -M_PI_2};
+
+ std::vector<DataAdapter::TimestampedPose> timestamped_robot_poses = {
+ {TimeInMs(5), ceres::examples::Pose2d{-1.0, 0.0, 0.0}},
+ {TimeInMs(10), ceres::examples::Pose2d{3.0, 0.0, 0.0}},
+ {TimeInMs(15), ceres::examples::Pose2d{4.0, 0.0, 0.0}},
+ {TimeInMs(20), ceres::examples::Pose2d{-1.0, 0.0, 0.0}}};
+ std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections =
+ {{TimeInMs(5),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{6.0, 0.0, M_PI}),
+ 0},
+ {TimeInMs(10),
+ PoseUtils::Pose2dToAffine3d(
+ ceres::examples::Pose2d{-8.0, 0.0, -M_PI_2}),
+ 1},
+ {TimeInMs(15),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{1.0, 0.0, M_PI}),
+ 0}};
+ auto target_constraints =
+ DataAdapter::MatchTargetDetections(timestamped_robot_poses,
+ timestamped_target_detections)
+ .first;
+
+ frc971::vision::TargetMapper mapper(target_poses, target_constraints);
+ mapper.Solve(kFieldName);
+
+ ASSERT_EQ(mapper.target_poses().size(), 2);
+ EXPECT_POSE_NEAR(mapper.target_poses()[0], MakePose(5.0, 0.0, M_PI));
+ EXPECT_POSE_NEAR(mapper.target_poses()[1], MakePose(-5.0, 0.0, -M_PI_2));
+}
+
+TEST(TargetMapperTest, TwoTargetsOneNoisyConstraint) {
+ std::map<TargetMapper::TargetId, ceres::examples::Pose2d> target_poses;
+ target_poses[0] = ceres::examples::Pose2d{5.0, 0.0, M_PI};
+ target_poses[1] = ceres::examples::Pose2d{-5.0, 0.0, 0.0};
+
+ std::vector<DataAdapter::TimestampedPose> timestamped_robot_poses = {
+ {TimeInMs(5), ceres::examples::Pose2d{1.99, 0.0, 0.0}},
+ {TimeInMs(10), ceres::examples::Pose2d{-1.0, 0.0, 0.0}},
+ {TimeInMs(15), ceres::examples::Pose2d{-1.01, -0.01, 0.004}}};
+ std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections =
+ {{TimeInMs(5),
+ PoseUtils::Pose2dToAffine3d(
+ ceres::examples::Pose2d{3.01, 0.001, M_PI - 0.001}),
+ 0},
+ {TimeInMs(10),
+ PoseUtils::Pose2dToAffine3d(ceres::examples::Pose2d{-4.01, 0.0, 0.0}),
+ 1}};
+
+ auto target_constraints =
+ DataAdapter::MatchTargetDetections(timestamped_robot_poses,
+ timestamped_target_detections)
+ .first;
+
+ frc971::vision::TargetMapper mapper(target_poses, target_constraints);
+ mapper.Solve(kFieldName);
+
+ ASSERT_EQ(mapper.target_poses().size(), 2);
+ EXPECT_POSE_NEAR(mapper.target_poses()[0], MakePose(5.0, 0.0, M_PI));
+ EXPECT_POSE_NEAR(mapper.target_poses()[1], MakePose(-5.0, 0.0, 0.0));
+}
+
+TEST(TargetMapperTest, MultiTargetCircleMotion) {
+ // Build set of target locations wrt global origin
+ // For simplicity, do this on a grid of the field
+ double field_half_length = 7.5; // half length of the field
+ double field_half_width = 5.0; // half width of the field
+ std::map<TargetMapper::TargetId, ceres::examples::Pose2d> target_poses;
+ std::vector<TargetMapper::TargetPose> actual_target_poses;
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ TargetMapper::TargetId target_id = i * 3 + j;
+ TargetMapper::TargetPose target_pose{
+ target_id, ceres::examples::Pose2d{field_half_length * (1 - i),
+ field_half_width * (1 - j), 0.0}};
+ actual_target_poses.emplace_back(target_pose);
+ target_poses[target_id] = target_pose.pose;
+ VLOG(2) << "VERTEX_SE2 " << target_id << " " << target_pose.pose.x << " "
+ << target_pose.pose.y << " " << target_pose.pose.yaw_radians;
+ }
+ }
+
+ // Now, create a bunch of robot poses and target
+ // observations
+ size_t dt = 1;
+
+ std::vector<DataAdapter::TimestampedPose> timestamped_robot_poses;
+ std::vector<DataAdapter::TimestampedDetection> timestamped_target_detections;
+
+ constexpr size_t kTotalSteps = 100;
+ for (size_t step_count = 0; step_count < kTotalSteps; step_count++) {
+ size_t t = dt * step_count;
+ // Circle clockwise around the center of the field
+ double robot_theta = t;
+ double robot_x = (field_half_length / 2.0) * cos(robot_theta);
+ double robot_y = (-field_half_width / 2.0) * sin(robot_theta);
+
+ ceres::examples::Pose2d robot_pose{robot_x, robot_y, robot_theta};
+ for (TargetMapper::TargetPose target_pose : actual_target_poses) {
+ TargetMapper::TargetPose target_detection = {
+ .id = target_pose.id,
+ .pose = PoseUtils::ComputeRelativePose(robot_pose, target_pose.pose)};
+ if (TargetIsInView(target_detection)) {
+ // Define random generator with Gaussian
+ // distribution
+ const double mean = 0.0;
+ const double stddev = 1.0;
+ // Can play with this to see how it impacts
+ // randomness
+ constexpr double kNoiseScale = 0.01;
+ std::default_random_engine generator(aos::testing::RandomSeed());
+ std::normal_distribution<double> dist(mean, stddev);
+
+ target_detection.pose.x += dist(generator) * kNoiseScale;
+ target_detection.pose.y += dist(generator) * kNoiseScale;
+ robot_pose.x += dist(generator) * kNoiseScale;
+ robot_pose.y += dist(generator) * kNoiseScale;
+
+ auto time_point =
+ aos::distributed_clock::time_point(std::chrono::milliseconds(t));
+ timestamped_robot_poses.emplace_back(DataAdapter::TimestampedPose{
+ .time = time_point, .pose = robot_pose});
+ timestamped_target_detections.emplace_back(
+ DataAdapter::TimestampedDetection{
+ .time = time_point,
+ .H_robot_target =
+ PoseUtils::Pose2dToAffine3d(target_detection.pose),
+ .id = target_detection.id});
+ }
+ }
+ }
+
+ {
+ // Add in a robot pose after all target poses
+ auto final_robot_pose =
+ timestamped_robot_poses[timestamped_robot_poses.size() - 1];
+ timestamped_robot_poses.emplace_back(DataAdapter::TimestampedPose{
+ .time = final_robot_pose.time + std::chrono::milliseconds(dt),
+ .pose = final_robot_pose.pose});
+ }
+
+ auto target_constraints =
+ DataAdapter::MatchTargetDetections(timestamped_robot_poses,
+ timestamped_target_detections)
+ .first;
+ frc971::vision::TargetMapper mapper(target_poses, target_constraints);
+ mapper.Solve(kFieldName);
+
+ for (auto [target_pose_id, mapper_target_pose] : mapper.target_poses()) {
+ TargetMapper::TargetPose actual_target_pose =
+ TargetMapper::GetTargetPoseById(actual_target_poses, target_pose_id)
+ .value();
+ EXPECT_POSE_NEAR(mapper_target_pose, actual_target_pose.pose);
+ }
+
+ //
+ // See what happens when we don't start with the
+ // correct values
+ //
+ for (auto [target_id, target_pose] : target_poses) {
+ // Skip first pose, since that needs to be correct
+ // and is fixed in the solver
+ if (target_id != 0) {
+ ceres::examples::Pose2d bad_pose{0.0, 0.0, M_PI / 2.0};
+ target_poses[target_id] = bad_pose;
+ }
+ }
+
+ frc971::vision::TargetMapper mapper_bad_poses(target_poses,
+ target_constraints);
+ mapper_bad_poses.Solve(kFieldName);
+
+ for (auto [target_pose_id, mapper_target_pose] :
+ mapper_bad_poses.target_poses()) {
+ TargetMapper::TargetPose actual_target_pose =
+ TargetMapper::GetTargetPoseById(actual_target_poses, target_pose_id)
+ .value();
+ EXPECT_POSE_NEAR(mapper_target_pose, actual_target_pose.pose);
+ }
+}
+
+} // namespace frc971::vision
diff --git a/frc971/vision/visualize_robot.cc b/frc971/vision/visualize_robot.cc
new file mode 100644
index 0000000..0bbe507
--- /dev/null
+++ b/frc971/vision/visualize_robot.cc
@@ -0,0 +1,74 @@
+#include "frc971/vision/visualize_robot.h"
+#include "glog/logging.h"
+
+#include <opencv2/calib3d.hpp>
+#include <opencv2/core/eigen.hpp>
+#include <opencv2/highgui/highgui.hpp>
+#include <opencv2/imgproc.hpp>
+
+namespace frc971 {
+namespace vision {
+
+cv::Point VisualizeRobot::ProjectPoint(Eigen::Vector3d T_world_point) {
+ // Map 3D point in world coordinates to camera frame
+ Eigen::Vector3d T_camera_point = H_world_viewpoint_.inverse() * T_world_point;
+
+ cv::Vec3d T_camera_point_cv;
+ cv::eigen2cv(T_camera_point, T_camera_point_cv);
+
+ // Project 3d point in camera frame via camera intrinsics
+ cv::Mat proj_point = camera_mat_ * cv::Mat(T_camera_point_cv);
+ cv::Point projected_point(
+ proj_point.at<double>(0, 0) / proj_point.at<double>(0, 2),
+ proj_point.at<double>(0, 1) / proj_point.at<double>(0, 2));
+ return projected_point;
+}
+
+void VisualizeRobot::DrawLine(Eigen::Vector3d start3d, Eigen::Vector3d end3d) {
+ cv::Point start2d = ProjectPoint(start3d);
+ cv::Point end2d = ProjectPoint(end3d);
+
+ cv::line(image_, start2d, end2d, cv::Scalar(0, 0, 255));
+}
+
+void VisualizeRobot::DrawFrameAxes(Eigen::Affine3d H_world_target,
+ std::string label, double axis_scale) {
+ // Map origin to display from global (world) frame to camera frame
+ Eigen::Affine3d H_viewpoint_target =
+ H_world_viewpoint_.inverse() * H_world_target;
+
+ // Extract into OpenCV vectors
+ cv::Mat H_viewpoint_target_mat;
+ cv::eigen2cv(H_viewpoint_target.matrix(), H_viewpoint_target_mat);
+
+ // Convert to opencv R, T for using drawFrameAxes
+ cv::Vec3d rvec, tvec;
+ tvec = H_viewpoint_target_mat(cv::Rect(3, 0, 1, 3));
+ cv::Mat r_mat = H_viewpoint_target_mat(cv::Rect(0, 0, 3, 3));
+ cv::Rodrigues(r_mat, rvec);
+
+ cv::drawFrameAxes(image_, camera_mat_, dist_coeffs_, rvec, tvec, axis_scale);
+
+ if (label != "") {
+ // Grab x axis direction
+ cv::Vec3d label_offset = r_mat.col(0);
+
+ // Find 3D coordinate of point at the end of the x-axis, and project it
+ cv::Mat label_coord_res =
+ camera_mat_ * cv::Mat(tvec + label_offset * axis_scale);
+ cv::Vec3d label_coord = label_coord_res.col(0);
+ label_coord[0] = label_coord[0] / label_coord[2];
+ label_coord[1] = label_coord[1] / label_coord[2];
+ cv::putText(image_, label, cv::Point(label_coord[0], label_coord[1]),
+ cv::FONT_HERSHEY_PLAIN, 1.0, cv::Scalar(0, 0, 255));
+ }
+}
+
+void VisualizeRobot::DrawBoardOutline(Eigen::Affine3d H_world_board,
+ std::string label) {
+ LOG(INFO) << "Not yet implemented; drawing axes only";
+ DrawFrameAxes(H_world_board, label);
+}
+
+} // namespace vision
+} // namespace frc971
diff --git a/frc971/vision/visualize_robot.h b/frc971/vision/visualize_robot.h
new file mode 100644
index 0000000..391a030
--- /dev/null
+++ b/frc971/vision/visualize_robot.h
@@ -0,0 +1,65 @@
+#ifndef FRC971_VISION_VISUALIZE_ROBOT_H_
+#define FRC971_VISION_VISUALIZE_ROBOT_H_
+
+#include <opencv2/core.hpp>
+#include <opencv2/highgui.hpp>
+#include <opencv2/imgproc.hpp>
+
+#include "Eigen/Dense"
+#include "Eigen/Geometry"
+
+namespace frc971 {
+namespace vision {
+
+// Helper class to visualize the coordinate frames associated with
+// the robot Based on a virtual camera viewpoint, and camera model,
+// this class can be used to draw 3D coordinate frames in a virtual
+// camera view.
+//
+// Mostly useful just for doing all the projection calculations
+// Leverages Eigen for transforms and opencv for drawing axes
+
+class VisualizeRobot {
+ public:
+ // Set image on which to draw
+ void SetImage(cv::Mat image) { image_ = image; }
+
+ // Set the viewpoint of the camera relative to a global origin
+ void SetViewpoint(Eigen::Affine3d view_origin) {
+ H_world_viewpoint_ = view_origin;
+ }
+
+ // Set camera parameters (for projection into a virtual view)
+ void SetCameraParameters(cv::Mat camera_intrinsics) {
+ camera_mat_ = camera_intrinsics;
+ }
+
+ // Set distortion coefficients (defaults to 0)
+ void SetDistortionCoefficients(cv::Mat dist_coeffs) {
+ dist_coeffs_ = dist_coeffs;
+ }
+
+ // Helper function to project a point in 3D to the virtual image coordinates
+ cv::Point ProjectPoint(Eigen::Vector3d point3d);
+
+ // Draw a line connecting two 3D points
+ void DrawLine(Eigen::Vector3d start, Eigen::Vector3d end);
+
+ // Draw coordinate frame for a target frame relative to the world frame
+ // Axes are drawn (x,y,z) -> (red, green, blue)
+ void DrawFrameAxes(Eigen::Affine3d H_world_target, std::string label = "",
+ double axis_scale = 0.25);
+
+ // TODO<Jim>: Need to implement this, and maybe DrawRobotOutline
+ void DrawBoardOutline(Eigen::Affine3d H_world_board, std::string label = "");
+
+ Eigen::Affine3d H_world_viewpoint_; // Where to view the world from
+ cv::Mat image_; // Image to draw on
+ cv::Mat camera_mat_; // Virtual camera intrinsics (defines fov, center)
+ cv::Mat dist_coeffs_; // Distortion coefficients, if desired (only used in
+ // DrawFrameAxes
+};
+} // namespace vision
+} // namespace frc971
+
+#endif // FRC971_VISION_VISUALIZE_ROBOT_H_
diff --git a/frc971/vision/visualize_robot_sample.cc b/frc971/vision/visualize_robot_sample.cc
new file mode 100644
index 0000000..dc38352
--- /dev/null
+++ b/frc971/vision/visualize_robot_sample.cc
@@ -0,0 +1,72 @@
+#include "frc971/vision/visualize_robot.h"
+
+#include "aos/init.h"
+#include "aos/logging/logging.h"
+#include "glog/logging.h"
+
+#include "Eigen/Dense"
+
+#include <math.h>
+#include <opencv2/aruco.hpp>
+#include <opencv2/aruco/charuco.hpp>
+#include <opencv2/calib3d.hpp>
+#include <opencv2/core/eigen.hpp>
+#include <opencv2/highgui/highgui.hpp>
+#include <opencv2/imgproc.hpp>
+#include "aos/time/time.h"
+
+namespace frc971 {
+namespace vision {
+
+// Show / test the basics of visualizing the robot frames
+void Main(int /*argc*/, char ** /* argv */) {
+ VisualizeRobot vis_robot;
+
+ int image_width = 500;
+ cv::Mat image_mat =
+ cv::Mat::zeros(cv::Size(image_width, image_width), CV_8UC3);
+ vis_robot.SetImage(image_mat);
+
+ // 10 meters above the origin, rotated so the camera faces straight down
+ Eigen::Translation3d camera_trans(0, 0, 10.0);
+ Eigen::AngleAxisd camera_rot(M_PI, Eigen::Vector3d::UnitX());
+ Eigen::Affine3d camera_viewpoint = camera_trans * camera_rot;
+ vis_robot.SetViewpoint(camera_viewpoint);
+
+ cv::Mat camera_mat;
+ double focal_length = 1000.0;
+ double intr[] = {focal_length, 0.0, image_width / 2.0,
+ 0.0, focal_length, image_width / 2.0,
+ 0.0, 0.0, 1.0};
+ camera_mat = cv::Mat(3, 3, CV_64FC1, intr);
+ vis_robot.SetCameraParameters(camera_mat);
+
+ Eigen::Affine3d offset_rotate_origin(Eigen::Affine3d::Identity());
+
+ cv::Mat dist_coeffs = cv::Mat(1, 5, CV_64F, 0.0);
+ vis_robot.SetDistortionCoefficients(dist_coeffs);
+
+ // Go around the clock and plot the coordinate frame at different rotations
+ for (int i = 0; i < 12; i++) {
+ double angle = M_PI * double(i) / 6.0;
+ Eigen::Vector3d trans;
+ trans << 1.0 * cos(angle), 1.0 * sin(angle), 0.0;
+
+ offset_rotate_origin = Eigen::Translation3d(trans) *
+ Eigen::AngleAxisd(angle, Eigen::Vector3d::UnitX());
+
+ vis_robot.DrawFrameAxes(offset_rotate_origin, std::to_string(i));
+ }
+
+ // Display the result
+ cv::imshow("Display", image_mat);
+ cv::waitKey();
+}
+} // namespace vision
+} // namespace frc971
+
+int main(int argc, char **argv) {
+ aos::InitGoogle(&argc, &argv);
+
+ frc971::vision::Main(argc, argv);
+}
diff --git a/go.mod b/go.mod
index 09c71f4..b1720c2 100644
--- a/go.mod
+++ b/go.mod
@@ -7,27 +7,32 @@
github.com/golang/protobuf v1.5.2
github.com/google/flatbuffers v2.0.5+incompatible
google.golang.org/grpc v1.43.0
+ gorm.io/driver/postgres v1.3.7
+ gorm.io/gorm v1.23.5
)
require (
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/davecgh/go-spew v1.1.1
github.com/google/go-querystring v1.1.0 // indirect
- github.com/jackc/pgx v3.6.2+incompatible
github.com/phst/runfiles v0.0.0-20220125203201-388095b3a22d
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
- golang.org/x/text v0.3.6 // indirect
+ golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/protobuf v1.26.0 // indirect
)
require (
- github.com/cockroachdb/apd v1.1.0 // indirect
- github.com/gofrs/uuid v4.0.0+incompatible // indirect
- github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
- github.com/lib/pq v1.10.2 // indirect
- github.com/pkg/errors v0.8.1 // indirect
- github.com/shopspring/decimal v1.2.0 // indirect
- golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
+ github.com/jackc/chunkreader/v2 v2.0.1 // indirect
+ github.com/jackc/pgconn v1.12.1 // indirect
+ github.com/jackc/pgio v1.0.0 // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgproto3/v2 v2.3.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
+ github.com/jackc/pgtype v1.11.0 // indirect
+ github.com/jackc/pgx/v4 v4.16.1 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.4 // indirect
+ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
)
diff --git a/go.sum b/go.sum
index 7c08101..3082bdc 100644
--- a/go.sum
+++ b/go.sum
@@ -1,6 +1,8 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/buildkite/go-buildkite v2.2.0+incompatible h1:yEjSu1axFC88x4dbufhgMDsEnJztPWlLiZzEvzJggXc=
github.com/buildkite/go-buildkite v2.2.0+incompatible/go.mod h1:WTV0aX5KnQ9ofsKMg2CLUBLJNsQ0RwOEKPhrXXZWPcE=
@@ -17,6 +19,9 @@
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -27,6 +32,9 @@
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -57,41 +65,145 @@
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=
-github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
-github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
-github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
+github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=
+github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono=
+github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
+github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=
+github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
+github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=
+github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
+github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=
+github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
+github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/phst/runfiles v0.0.0-20220125203201-388095b3a22d h1:N5aMcF9W9AjW4ed+PJhA7+FjdgPa9gJ+St3mNu2tq1Q=
github.com/phst/runfiles v0.0.0-20220125203201-388095b3a22d/go.mod h1:+oijTyzCf6Qe7sczsCOuoeX11IxZ+UkXXlhLrfyHlzg=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -102,22 +214,44 @@
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -149,8 +283,18 @@
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/postgres v1.3.7 h1:FKF6sIMDHDEvvMF/XJvbnCl0nu6KSKUaPXevJ4r+VYQ=
+gorm.io/driver/postgres v1.3.7/go.mod h1:f02ympjIcgtHEGFMZvdgTxODZ9snAHDb4hXfigBVuNI=
+gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
+gorm.io/gorm v1.23.5 h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM=
+gorm.io/gorm v1.23.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
diff --git a/go_deps.bzl b/go_deps.bzl
index 9d37e42..2869b5e 100644
--- a/go_deps.bzl
+++ b/go_deps.bzl
@@ -5,8 +5,8 @@
maybe_override_go_dep(
name = "co_honnef_go_tools",
importpath = "honnef.co/go/tools",
- sum = "h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=",
- version = "v0.0.0-20190523083050-ea95bdfd59fc",
+ sum = "h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=",
+ version = "v0.0.1-2019.2.3",
)
maybe_override_go_dep(
name = "com_github_antihax_optional",
@@ -69,6 +69,18 @@
version = "v1.1.0",
)
maybe_override_go_dep(
+ name = "com_github_coreos_go_systemd",
+ importpath = "github.com/coreos/go-systemd",
+ sum = "h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=",
+ version = "v0.0.0-20190719114852-fd7a80b32e1f",
+ )
+ maybe_override_go_dep(
+ name = "com_github_creack_pty",
+ importpath = "github.com/creack/pty",
+ sum = "h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=",
+ version = "v1.1.7",
+ )
+ maybe_override_go_dep(
name = "com_github_davecgh_go_spew",
importpath = "github.com/davecgh/go-spew",
sum = "h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=",
@@ -93,6 +105,24 @@
version = "v1.0.0",
)
maybe_override_go_dep(
+ name = "com_github_go_kit_log",
+ importpath = "github.com/go-kit/log",
+ sum = "h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ=",
+ version = "v0.1.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_go_logfmt_logfmt",
+ importpath = "github.com/go-logfmt/logfmt",
+ sum = "h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=",
+ version = "v0.5.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_go_stack_stack",
+ importpath = "github.com/go-stack/stack",
+ sum = "h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=",
+ version = "v1.8.0",
+ )
+ maybe_override_go_dep(
name = "com_github_gofrs_uuid",
importpath = "github.com/gofrs/uuid",
sum = "h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=",
@@ -129,6 +159,12 @@
version = "v1.1.0",
)
maybe_override_go_dep(
+ name = "com_github_google_renameio",
+ importpath = "github.com/google/renameio",
+ sum = "h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=",
+ version = "v0.1.0",
+ )
+ maybe_override_go_dep(
name = "com_github_google_uuid",
importpath = "github.com/google/uuid",
sum = "h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=",
@@ -141,16 +177,118 @@
version = "v1.16.0",
)
maybe_override_go_dep(
- name = "com_github_jackc_fake",
- importpath = "github.com/jackc/fake",
- sum = "h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=",
- version = "v0.0.0-20150926172116-812a484cc733",
+ name = "com_github_jackc_chunkreader",
+ importpath = "github.com/jackc/chunkreader",
+ sum = "h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=",
+ version = "v1.0.0",
)
maybe_override_go_dep(
- name = "com_github_jackc_pgx",
- importpath = "github.com/jackc/pgx",
- sum = "h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=",
- version = "v3.6.2+incompatible",
+ name = "com_github_jackc_chunkreader_v2",
+ importpath = "github.com/jackc/chunkreader/v2",
+ sum = "h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=",
+ version = "v2.0.1",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_pgconn",
+ importpath = "github.com/jackc/pgconn",
+ sum = "h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8=",
+ version = "v1.12.1",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_pgio",
+ importpath = "github.com/jackc/pgio",
+ sum = "h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=",
+ version = "v1.0.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_pgmock",
+ importpath = "github.com/jackc/pgmock",
+ sum = "h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=",
+ version = "v0.0.0-20210724152146-4ad1a8207f65",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_pgpassfile",
+ importpath = "github.com/jackc/pgpassfile",
+ sum = "h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=",
+ version = "v1.0.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_pgproto3",
+ importpath = "github.com/jackc/pgproto3",
+ sum = "h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=",
+ version = "v1.1.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_pgproto3_v2",
+ importpath = "github.com/jackc/pgproto3/v2",
+ sum = "h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y=",
+ version = "v2.3.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_pgservicefile",
+ importpath = "github.com/jackc/pgservicefile",
+ sum = "h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=",
+ version = "v0.0.0-20200714003250-2b9c44734f2b",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_pgtype",
+ importpath = "github.com/jackc/pgtype",
+ sum = "h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs=",
+ version = "v1.11.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_pgx_v4",
+ importpath = "github.com/jackc/pgx/v4",
+ sum = "h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y=",
+ version = "v4.16.1",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jackc_puddle",
+ importpath = "github.com/jackc/puddle",
+ sum = "h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw=",
+ version = "v1.2.1",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jinzhu_inflection",
+ importpath = "github.com/jinzhu/inflection",
+ sum = "h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=",
+ version = "v1.0.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_jinzhu_now",
+ importpath = "github.com/jinzhu/now",
+ sum = "h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=",
+ version = "v1.1.4",
+ )
+ maybe_override_go_dep(
+ name = "com_github_kisielk_gotool",
+ importpath = "github.com/kisielk/gotool",
+ sum = "h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=",
+ version = "v1.0.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_konsorten_go_windows_terminal_sequences",
+ importpath = "github.com/konsorten/go-windows-terminal-sequences",
+ sum = "h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=",
+ version = "v1.0.2",
+ )
+ maybe_override_go_dep(
+ name = "com_github_kr_pretty",
+ importpath = "github.com/kr/pretty",
+ sum = "h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=",
+ version = "v0.1.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_kr_pty",
+ importpath = "github.com/kr/pty",
+ sum = "h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=",
+ version = "v1.1.8",
+ )
+ maybe_override_go_dep(
+ name = "com_github_kr_text",
+ importpath = "github.com/kr/text",
+ sum = "h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=",
+ version = "v0.1.0",
)
maybe_override_go_dep(
name = "com_github_lib_pq",
@@ -159,6 +297,24 @@
version = "v1.10.2",
)
maybe_override_go_dep(
+ name = "com_github_masterminds_semver_v3",
+ importpath = "github.com/Masterminds/semver/v3",
+ sum = "h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=",
+ version = "v3.1.1",
+ )
+ maybe_override_go_dep(
+ name = "com_github_mattn_go_colorable",
+ importpath = "github.com/mattn/go-colorable",
+ sum = "h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=",
+ version = "v0.1.6",
+ )
+ maybe_override_go_dep(
+ name = "com_github_mattn_go_isatty",
+ importpath = "github.com/mattn/go-isatty",
+ sum = "h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=",
+ version = "v0.0.12",
+ )
+ maybe_override_go_dep(
name = "com_github_phst_runfiles",
importpath = "github.com/phst/runfiles",
sum = "h1:N5aMcF9W9AjW4ed+PJhA7+FjdgPa9gJ+St3mNu2tq1Q=",
@@ -189,16 +345,46 @@
version = "v1.2.0",
)
maybe_override_go_dep(
+ name = "com_github_rogpeppe_go_internal",
+ importpath = "github.com/rogpeppe/go-internal",
+ sum = "h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=",
+ version = "v1.3.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_rs_xid",
+ importpath = "github.com/rs/xid",
+ sum = "h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=",
+ version = "v1.2.1",
+ )
+ maybe_override_go_dep(
+ name = "com_github_rs_zerolog",
+ importpath = "github.com/rs/zerolog",
+ sum = "h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=",
+ version = "v1.15.0",
+ )
+ maybe_override_go_dep(
+ name = "com_github_satori_go_uuid",
+ importpath = "github.com/satori/go.uuid",
+ sum = "h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=",
+ version = "v1.2.0",
+ )
+ maybe_override_go_dep(
name = "com_github_shopspring_decimal",
importpath = "github.com/shopspring/decimal",
sum = "h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=",
version = "v1.2.0",
)
maybe_override_go_dep(
+ name = "com_github_sirupsen_logrus",
+ importpath = "github.com/sirupsen/logrus",
+ sum = "h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=",
+ version = "v1.4.2",
+ )
+ maybe_override_go_dep(
name = "com_github_stretchr_objx",
importpath = "github.com/stretchr/objx",
- sum = "h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=",
- version = "v0.1.0",
+ sum = "h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=",
+ version = "v0.2.0",
)
maybe_override_go_dep(
name = "com_github_stretchr_testify",
@@ -207,6 +393,12 @@
version = "v1.7.0",
)
maybe_override_go_dep(
+ name = "com_github_zenazn_goji",
+ importpath = "github.com/zenazn/goji",
+ sum = "h1:RSQQAbXGArQ0dIDEq+PI6WqN6if+5KHu6x2Cx/GXLTQ=",
+ version = "v0.9.0",
+ )
+ maybe_override_go_dep(
name = "com_google_cloud_go",
importpath = "cloud.google.com/go",
sum = "h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=",
@@ -215,8 +407,20 @@
maybe_override_go_dep(
name = "in_gopkg_check_v1",
importpath = "gopkg.in/check.v1",
- sum = "h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=",
- version = "v0.0.0-20161208181325-20d25e280405",
+ sum = "h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=",
+ version = "v1.0.0-20180628173108-788fd7840127",
+ )
+ maybe_override_go_dep(
+ name = "in_gopkg_errgo_v2",
+ importpath = "gopkg.in/errgo.v2",
+ sum = "h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=",
+ version = "v2.1.0",
+ )
+ maybe_override_go_dep(
+ name = "in_gopkg_inconshreveable_log15_v2",
+ importpath = "gopkg.in/inconshreveable/log15.v2",
+ sum = "h1:RlWgLqCMMIYYEVcAR5MDsuHlVkaIPDAF+5Dehzg8L5A=",
+ version = "v2.0.0-20180818164646-67afb5ed74ec",
)
maybe_override_go_dep(
name = "in_gopkg_yaml_v2",
@@ -231,6 +435,18 @@
version = "v3.0.0-20200313102051-9f266ea9e77c",
)
maybe_override_go_dep(
+ name = "io_gorm_driver_postgres",
+ importpath = "gorm.io/driver/postgres",
+ sum = "h1:FKF6sIMDHDEvvMF/XJvbnCl0nu6KSKUaPXevJ4r+VYQ=",
+ version = "v1.3.7",
+ )
+ maybe_override_go_dep(
+ name = "io_gorm_gorm",
+ importpath = "gorm.io/gorm",
+ sum = "h1:TnlF26wScKSvknUC/Rn8t0NLLM22fypYBlvj1+aH6dM=",
+ version = "v1.23.5",
+ )
+ maybe_override_go_dep(
name = "io_opentelemetry_go_proto_otlp",
importpath = "go.opentelemetry.io/proto/otlp",
sum = "h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8=",
@@ -263,8 +479,8 @@
maybe_override_go_dep(
name = "org_golang_x_crypto",
importpath = "golang.org/x/crypto",
- sum = "h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=",
- version = "v0.0.0-20210711020723-a769d52b0f97",
+ sum = "h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=",
+ version = "v0.0.0-20210921155107-089bfa567519",
)
maybe_override_go_dep(
name = "org_golang_x_exp",
@@ -275,8 +491,14 @@
maybe_override_go_dep(
name = "org_golang_x_lint",
importpath = "golang.org/x/lint",
- sum = "h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=",
- version = "v0.0.0-20190313153728-d0100b6bd8b3",
+ sum = "h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=",
+ version = "v0.0.0-20190930215403-16217165b5de",
+ )
+ maybe_override_go_dep(
+ name = "org_golang_x_mod",
+ importpath = "golang.org/x/mod",
+ sum = "h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=",
+ version = "v0.1.1-0.20191105210325-c90efee705ee",
)
maybe_override_go_dep(
name = "org_golang_x_net",
@@ -311,14 +533,14 @@
maybe_override_go_dep(
name = "org_golang_x_text",
importpath = "golang.org/x/text",
- sum = "h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=",
- version = "v0.3.6",
+ sum = "h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=",
+ version = "v0.3.7",
)
maybe_override_go_dep(
name = "org_golang_x_tools",
importpath = "golang.org/x/tools",
- sum = "h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=",
- version = "v0.0.0-20190524140312-2c0ae7006135",
+ sum = "h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE=",
+ version = "v0.0.0-20200103221440-774c71fcf114",
)
maybe_override_go_dep(
name = "org_golang_x_xerrors",
@@ -326,3 +548,27 @@
sum = "h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=",
version = "v0.0.0-20200804184101-5ec99f83aff1",
)
+ maybe_override_go_dep(
+ name = "org_uber_go_atomic",
+ importpath = "go.uber.org/atomic",
+ sum = "h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=",
+ version = "v1.6.0",
+ )
+ maybe_override_go_dep(
+ name = "org_uber_go_multierr",
+ importpath = "go.uber.org/multierr",
+ sum = "h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=",
+ version = "v1.5.0",
+ )
+ maybe_override_go_dep(
+ name = "org_uber_go_tools",
+ importpath = "go.uber.org/tools",
+ sum = "h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=",
+ version = "v0.0.0-20190618225709-2cfd321de3ee",
+ )
+ maybe_override_go_dep(
+ name = "org_uber_go_zap",
+ importpath = "go.uber.org/zap",
+ sum = "h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=",
+ version = "v1.13.0",
+ )
diff --git a/scouting/db/BUILD b/scouting/db/BUILD
index 7fbd2e2..154cab6 100644
--- a/scouting/db/BUILD
+++ b/scouting/db/BUILD
@@ -6,7 +6,12 @@
importpath = "github.com/frc971/971-Robot-Code/scouting/db",
target_compatible_with = ["@platforms//cpu:x86_64"],
visibility = ["//visibility:public"],
- deps = ["@com_github_jackc_pgx//stdlib"],
+ deps = [
+ "@io_gorm_driver_postgres//:postgres",
+ "@io_gorm_gorm//:gorm",
+ "@io_gorm_gorm//clause",
+ "@io_gorm_gorm//logger",
+ ],
)
go_test(
@@ -18,4 +23,5 @@
],
embed = [":db"],
target_compatible_with = ["@platforms//cpu:x86_64"],
+ deps = ["@com_github_davecgh_go_spew//spew"],
)
diff --git a/scouting/db/db.go b/scouting/db/db.go
index 3d514e3..55f2310 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -1,33 +1,50 @@
package db
import (
- "database/sql"
"errors"
"fmt"
- _ "github.com/jackc/pgx/stdlib"
+ "gorm.io/driver/postgres"
+ "gorm.io/gorm"
+ "gorm.io/gorm/clause"
+ "gorm.io/gorm/logger"
)
type Database struct {
- *sql.DB
+ *gorm.DB
}
type Match struct {
- MatchNumber, SetNumber int32
- CompLevel string
+ // TODO(phil): Rework this be be one team per row.
+ // Makes queries much simpler.
+ MatchNumber int32 `gorm:"primaryKey"`
+ SetNumber int32 `gorm:"primaryKey"`
+ CompLevel string `gorm:"primaryKey"`
R1, R2, R3, B1, B2, B3 int32
}
type Shift struct {
- MatchNumber int32
+ MatchNumber int32 `gorm:"primaryKey"`
R1scouter, R2scouter, R3scouter, B1scouter, B2scouter, B3scouter string
}
type Stats struct {
- TeamNumber, MatchNumber, SetNumber int32
- CompLevel string
- StartingQuadrant int32
- AutoBallPickedUp [5]bool
+ TeamNumber int32 `gorm:"primaryKey"`
+ MatchNumber int32 `gorm:"primaryKey"`
+ SetNumber int32 `gorm:"primaryKey"`
+ CompLevel string `gorm:"primaryKey"`
+ StartingQuadrant int32
+ // This field is for the balls picked up during auto. Use this field
+ // when using this library. Ignore the AutoBallPickedUpX fields below.
+ AutoBallPickedUp [5]bool `gorm:"-:all"`
+ // These fields are internal implementation details. Do not use these.
+ // TODO(phil): Figure out how to use the JSON gorm serializer instead
+ // of manually serializing/deserializing these.
+ AutoBallPickedUp1 bool
+ AutoBallPickedUp2 bool
+ AutoBallPickedUp3 bool
+ AutoBallPickedUp4 bool
+ AutoBallPickedUp5 bool
// TODO(phil): Re-order auto and teleop fields so auto comes first.
ShotsMissed, UpperGoalShots, LowerGoalShots int32
ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto int32
@@ -50,233 +67,85 @@
}
type NotesData struct {
- TeamNumber int32
- Notes []string
+ ID uint `gorm:"primaryKey"`
+ TeamNumber int32
+ Notes string
+ GoodDriving bool
+ BadDriving bool
+ SketchyClimb bool
+ SolidClimb bool
+ GoodDefense bool
+ BadDefense bool
}
type Ranking struct {
- TeamNumber int
+ TeamNumber int `gorm:"primaryKey"`
Losses, Wins, Ties int32
Rank, Dq int32
}
+type DriverRankingData struct {
+ // Each entry in the table is a single scout's ranking.
+ // Multiple scouts can submit a driver ranking for the same
+ // teams in the same match.
+ // The teams being ranked are stored in Rank1, Rank2, Rank3,
+ // Rank1 being the best driving and Rank3 being the worst driving.
+
+ ID uint `gorm:"primaryKey"`
+ MatchNumber int32
+ Rank1 int32
+ Rank2 int32
+ Rank3 int32
+}
+
// Opens a database at the specified port on localhost. We currently don't
// support connecting to databases on other hosts.
func NewDatabase(user string, password string, port int) (*Database, error) {
var err error
database := new(Database)
- psqlInfo := fmt.Sprintf("postgres://%s:%s@localhost:%d/postgres", user, password, port)
- database.DB, err = sql.Open("pgx", psqlInfo)
+ dsn := fmt.Sprintf("host=localhost user=%s password=%s dbname=postgres port=%d sslmode=disable", user, password, port)
+ database.DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
+ Logger: logger.Default.LogMode(logger.Silent),
+ })
if err != nil {
+ database.Delete()
return nil, errors.New(fmt.Sprint("Failed to connect to postgres: ", err))
}
- statement, err := database.Prepare("CREATE TABLE IF NOT EXISTS matches (" +
- "MatchNumber INTEGER, " +
- "SetNumber INTEGER, " +
- "CompLevel VARCHAR, " +
- "R1 INTEGER, " +
- "R2 INTEGER, " +
- "R3 INTEGER, " +
- "B1 INTEGER, " +
- "B2 INTEGER, " +
- "B3 INTEGER, " +
- "PRIMARY KEY (MatchNumber, SetNumber, CompLevel))")
+ err = database.AutoMigrate(&Match{}, &Shift{}, &Stats{}, &NotesData{}, &Ranking{}, &DriverRankingData{})
if err != nil {
- database.Close()
- return nil, errors.New(fmt.Sprint("Failed to prepare matches table creation: ", err))
- }
- defer statement.Close()
-
- _, err = statement.Exec()
- if err != nil {
- database.Close()
- return nil, errors.New(fmt.Sprint("Failed to create matches table: ", err))
- }
-
- statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS shift_schedule (" +
- "id SERIAL PRIMARY KEY, " +
- "MatchNumber INTEGER, " +
- "R1Scouter VARCHAR, " +
- "R2Scouter VARCHAR, " +
- "R3Scouter VARCHAR, " +
- "B1Scouter VARCHAR, " +
- "B2Scouter VARCHAR, " +
- "B3scouter VARCHAR)")
- if err != nil {
- database.Close()
- return nil, errors.New(fmt.Sprint("Failed to prepare shift schedule table creation: ", err))
- }
- defer statement.Close()
-
- _, err = statement.Exec()
- if err != nil {
- database.Close()
- return nil, errors.New(fmt.Sprint("Failed to create shift schedule table: ", err))
- }
-
- statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS team_match_stats (" +
- "TeamNumber INTEGER, " +
- "MatchNumber INTEGER, " +
- "SetNumber INTEGER, " +
- "CompLevel VARCHAR, " +
- "StartingQuadrant INTEGER, " +
- "AutoBall1PickedUp BOOLEAN, " +
- "AutoBall2PickedUp BOOLEAN, " +
- "AutoBall3PickedUp BOOLEAN, " +
- "AutoBall4PickedUp BOOLEAN, " +
- "AutoBall5PickedUp BOOLEAN, " +
- "ShotsMissed INTEGER, " +
- "UpperGoalShots INTEGER, " +
- "LowerGoalShots INTEGER, " +
- "ShotsMissedAuto INTEGER, " +
- "UpperGoalAuto INTEGER, " +
- "LowerGoalAuto INTEGER, " +
- "PlayedDefense INTEGER, " +
- "DefenseReceivedScore INTEGER, " +
- "Climbing INTEGER, " +
- "Comment VARCHAR, " +
- "CollectedBy VARCHAR, " +
- "PRIMARY KEY (TeamNumber, MatchNumber, SetNumber, CompLevel))")
- if err != nil {
- database.Close()
- return nil, errors.New(fmt.Sprint("Failed to prepare stats table creation: ", err))
- }
- defer statement.Close()
-
- _, err = statement.Exec()
- if err != nil {
- database.Close()
- return nil, errors.New(fmt.Sprint("Failed to create team_match_stats table: ", err))
- }
-
- statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS team_notes (" +
- "id SERIAL PRIMARY KEY, " +
- "TeamNumber INTEGER, " +
- "Notes TEXT)")
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to prepare notes table creation: ", err))
- }
- defer statement.Close()
-
- _, err = statement.Exec()
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to create notes table: ", err))
- }
-
- statement, err = database.Prepare("CREATE TABLE IF NOT EXISTS rankings (" +
- "id SERIAL PRIMARY KEY, " +
- "Losses INTEGER, " +
- "Wins INTEGER, " +
- "Ties INTEGER, " +
- "Rank INTEGER, " +
- "Dq INTEGER, " +
- "TeamNumber INTEGER)")
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to prepare rankings table creation: ", err))
- }
- defer statement.Close()
-
- _, err = statement.Exec()
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to create rankings table: ", err))
+ database.Delete()
+ return nil, errors.New(fmt.Sprint("Failed to create/migrate tables: ", err))
}
return database, nil
}
func (database *Database) Delete() error {
- statement, err := database.Prepare("DROP TABLE IF EXISTS matches")
+ sql, err := database.DB.DB()
if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare dropping matches table: ", err))
+ return err
}
- _, err = statement.Exec()
- if err != nil {
- return errors.New(fmt.Sprint("Failed to drop matches table: ", err))
- }
-
- statement, err = database.Prepare("DROP TABLE IF EXISTS shift_schedule")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare dropping shifts table: ", err))
- }
- _, err = statement.Exec()
- if err != nil {
- return errors.New(fmt.Sprint("Failed to drop shifts table: ", err))
- }
-
- statement, err = database.Prepare("DROP TABLE IF EXISTS team_match_stats")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare dropping stats table: ", err))
- }
- _, err = statement.Exec()
- if err != nil {
- return errors.New(fmt.Sprint("Failed to drop stats table: ", err))
- }
-
- statement, err = database.Prepare("DROP TABLE IF EXISTS team_notes")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare dropping notes table: ", err))
- }
- _, err = statement.Exec()
- if err != nil {
- return errors.New(fmt.Sprint("Failed to drop notes table: ", err))
- }
- return nil
-
- statement, err = database.Prepare("DROP TABLE IF EXISTS rankings")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare dropping rankings table: ", err))
- }
- _, err = statement.Exec()
- if err != nil {
- return errors.New(fmt.Sprint("Failed to drop rankings table: ", err))
- }
- return nil
+ return sql.Close()
}
-// This function will also populate the Stats table with six empty rows every time a match is added
-func (database *Database) AddToMatch(m Match) error {
- statement, err := database.Prepare("INSERT INTO matches(" +
- "MatchNumber, SetNumber, CompLevel, " +
- "R1, R2, R3, B1, B2, B3) " +
- "VALUES (" +
- "$1, $2, $3, " +
- "$4, $5, $6, $7, $8, $9) " +
- "ON CONFLICT (MatchNumber, SetNumber, CompLevel) DO UPDATE SET " +
- "R1 = EXCLUDED.R1, R2 = EXCLUDED.R2, R3 = EXCLUDED.R3, " +
- "B1 = EXCLUDED.B1, B2 = EXCLUDED.B2, B3 = EXCLUDED.B3")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare insertion into match database: ", err))
- }
- defer statement.Close()
+func (database *Database) SetDebugLogLevel() {
+ database.DB.Logger = database.DB.Logger.LogMode(logger.Info)
+}
- _, err = statement.Exec(m.MatchNumber, m.SetNumber, m.CompLevel,
- m.R1, m.R2, m.R3, m.B1, m.B2, m.B3)
- if err != nil {
- return errors.New(fmt.Sprint("Failed to insert into match database: ", err))
- }
- return nil
+func (database *Database) AddToMatch(m Match) error {
+ result := database.Clauses(clause.OnConflict{
+ UpdateAll: true,
+ }).Create(&m)
+ return result.Error
}
func (database *Database) AddToShift(sh Shift) error {
- statement, err := database.Prepare("INSERT INTO shift_schedule(" +
- "MatchNumber, " +
- "R1scouter, R2scouter, R3scouter, B1scouter, B2scouter, B3scouter) " +
- "VALUES (" +
- "$1, " +
- "$2, $3, $4, $5, $6, $7)")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare insertion into shift database: ", err))
- }
- defer statement.Close()
-
- _, err = statement.Exec(sh.MatchNumber,
- sh.R1scouter, sh.R2scouter, sh.R3scouter, sh.B1scouter, sh.B2scouter, sh.B3scouter)
- if err != nil {
- return errors.New(fmt.Sprint("Failed to insert into shift database: ", err))
- }
- return nil
+ result := database.Clauses(clause.OnConflict{
+ UpdateAll: true,
+ }).Create(&sh)
+ return result.Error
}
func (database *Database) AddToStats(s Stats) error {
@@ -297,304 +166,139 @@
" in match ", s.MatchNumber, " in the schedule."))
}
- statement, err := database.Prepare("INSERT INTO team_match_stats(" +
- "TeamNumber, MatchNumber, SetNumber, CompLevel, " +
- "StartingQuadrant, " +
- "AutoBall1PickedUp, AutoBall2PickedUp, AutoBall3PickedUp, " +
- "AutoBall4PickedUp, AutoBall5PickedUp, " +
- "ShotsMissed, UpperGoalShots, LowerGoalShots, " +
- "ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto, " +
- "PlayedDefense, DefenseReceivedScore, Climbing, " +
- "Comment, CollectedBy) " +
- "VALUES (" +
- "$1, $2, $3, $4, " +
- "$5, " +
- "$6, $7, $8, " +
- "$9, $10, " +
- "$11, $12, $13, " +
- "$14, $15, $16, " +
- "$17, $18, $19, " +
- "$20, $21)")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare stats update statement: ", err))
- }
- defer statement.Close()
-
- _, err = statement.Exec(
- s.TeamNumber, s.MatchNumber, s.SetNumber, s.CompLevel,
- s.StartingQuadrant,
- s.AutoBallPickedUp[0], s.AutoBallPickedUp[1], s.AutoBallPickedUp[2],
- s.AutoBallPickedUp[3], s.AutoBallPickedUp[4],
- s.ShotsMissed, s.UpperGoalShots, s.LowerGoalShots,
- s.ShotsMissedAuto, s.UpperGoalAuto, s.LowerGoalAuto,
- s.PlayedDefense, s.DefenseReceivedScore, s.Climbing,
- s.Comment, s.CollectedBy)
- if err != nil {
- return errors.New(fmt.Sprint("Failed to update stats database: ", err))
- }
-
- return nil
+ // Unpack the auto balls array.
+ s.AutoBallPickedUp1 = s.AutoBallPickedUp[0]
+ s.AutoBallPickedUp2 = s.AutoBallPickedUp[1]
+ s.AutoBallPickedUp3 = s.AutoBallPickedUp[2]
+ s.AutoBallPickedUp4 = s.AutoBallPickedUp[3]
+ s.AutoBallPickedUp5 = s.AutoBallPickedUp[4]
+ result := database.Create(&s)
+ return result.Error
}
func (database *Database) AddOrUpdateRankings(r Ranking) error {
- statement, err := database.Prepare("UPDATE rankings SET " +
- "Losses = $1, Wins = $2, Ties = $3, " +
- "Rank = $4, Dq = $5, TeamNumber = $6 " +
- "WHERE TeamNumber = $6")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare rankings database update: ", err))
- }
- defer statement.Close()
-
- result, err := statement.Exec(r.Losses, r.Wins, r.Ties,
- r.Rank, r.Dq, r.TeamNumber)
- if err != nil {
- return errors.New(fmt.Sprint("Failed to update rankings database: ", err))
- }
-
- numRowsAffected, err := result.RowsAffected()
- if err != nil {
- return errors.New(fmt.Sprint("Failed to query rows affected: ", err))
- }
- if numRowsAffected == 0 {
- statement, err := database.Prepare("INSERT INTO rankings(" +
- "Losses, Wins, Ties, " +
- "Rank, Dq, TeamNumber) " +
- "VALUES (" +
- "$1, $2, $3, " +
- "$4, $5, $6)")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare insertion into rankings database: ", err))
- }
- defer statement.Close()
-
- _, err = statement.Exec(r.Losses, r.Wins, r.Ties,
- r.Rank, r.Dq, r.TeamNumber)
- if err != nil {
- return errors.New(fmt.Sprint("Failed to insert into rankings database: ", err))
- }
- }
-
- return nil
+ result := database.Clauses(clause.OnConflict{
+ UpdateAll: true,
+ }).Create(&r)
+ return result.Error
}
func (database *Database) ReturnMatches() ([]Match, error) {
- rows, err := database.Query("SELECT * FROM matches")
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to select from matches: ", err))
- }
- defer rows.Close()
-
- matches := make([]Match, 0)
- for rows.Next() {
- var match Match
- err := rows.Scan(&match.MatchNumber, &match.SetNumber, &match.CompLevel,
- &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err))
- }
- matches = append(matches, match)
- }
- return matches, nil
+ var matches []Match
+ result := database.Find(&matches)
+ return matches, result.Error
}
func (database *Database) ReturnAllShifts() ([]Shift, error) {
- rows, err := database.Query("SELECT * FROM shift_schedule")
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to select from shift: ", err))
- }
- defer rows.Close()
+ var shifts []Shift
+ result := database.Find(&shifts)
+ return shifts, result.Error
+}
- shifts := make([]Shift, 0)
- for rows.Next() {
- var shift Shift
- var id int
- err := rows.Scan(&id, &shift.MatchNumber,
- &shift.R1scouter, &shift.R2scouter, &shift.R3scouter, &shift.B1scouter, &shift.B2scouter, &shift.B3scouter)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to scan from shift: ", err))
- }
- shifts = append(shifts, shift)
+// Packs the stats. This really just consists of taking the individual auto
+// ball booleans and turning them into an array. The individual booleans are
+// cleared so that they don't affect struct comparisons.
+func packStats(stats *Stats) {
+ stats.AutoBallPickedUp = [5]bool{
+ stats.AutoBallPickedUp1,
+ stats.AutoBallPickedUp2,
+ stats.AutoBallPickedUp3,
+ stats.AutoBallPickedUp4,
+ stats.AutoBallPickedUp5,
}
- return shifts, nil
+ stats.AutoBallPickedUp1 = false
+ stats.AutoBallPickedUp2 = false
+ stats.AutoBallPickedUp3 = false
+ stats.AutoBallPickedUp4 = false
+ stats.AutoBallPickedUp5 = false
}
func (database *Database) ReturnStats() ([]Stats, error) {
- rows, err := database.Query("SELECT * FROM team_match_stats")
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to SELECT * FROM team_match_stats: ", err))
+ var stats []Stats
+ result := database.Find(&stats)
+ // Pack the auto balls array.
+ for i := range stats {
+ packStats(&stats[i])
}
- defer rows.Close()
-
- teams := make([]Stats, 0)
- for rows.Next() {
- var team Stats
- err = rows.Scan(
- &team.TeamNumber, &team.MatchNumber, &team.SetNumber, &team.CompLevel,
- &team.StartingQuadrant,
- &team.AutoBallPickedUp[0], &team.AutoBallPickedUp[1], &team.AutoBallPickedUp[2],
- &team.AutoBallPickedUp[3], &team.AutoBallPickedUp[4],
- &team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots,
- &team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto,
- &team.PlayedDefense, &team.DefenseReceivedScore, &team.Climbing,
- &team.Comment, &team.CollectedBy)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err))
- }
- teams = append(teams, team)
- }
- return teams, nil
+ return stats, result.Error
}
func (database *Database) ReturnRankings() ([]Ranking, error) {
- rows, err := database.Query("SELECT * FROM rankings")
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to SELECT * FROM rankings: ", err))
- }
- defer rows.Close()
-
- all_rankings := make([]Ranking, 0)
- for rows.Next() {
- var ranking Ranking
- var id int
- err = rows.Scan(&id,
- &ranking.Losses, &ranking.Wins, &ranking.Ties,
- &ranking.Rank, &ranking.Dq, &ranking.TeamNumber)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to scan from rankings: ", err))
- }
- all_rankings = append(all_rankings, ranking)
- }
- return all_rankings, nil
+ var rankins []Ranking
+ result := database.Find(&rankins)
+ return rankins, result.Error
}
func (database *Database) QueryMatches(teamNumber_ int32) ([]Match, error) {
- rows, err := database.Query("SELECT * FROM matches WHERE "+
- "R1 = $1 OR R2 = $2 OR R3 = $3 OR B1 = $4 OR B2 = $5 OR B3 = $6",
- teamNumber_, teamNumber_, teamNumber_, teamNumber_, teamNumber_, teamNumber_)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to select from matches for team: ", err))
- }
- defer rows.Close()
-
var matches []Match
- for rows.Next() {
- var match Match
- err = rows.Scan(&match.MatchNumber, &match.SetNumber, &match.CompLevel,
- &match.R1, &match.R2, &match.R3, &match.B1, &match.B2, &match.B3)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err))
- }
- matches = append(matches, match)
- }
- return matches, nil
+ result := database.
+ Where("r1 = $1 OR r2 = $1 OR r3 = $1 OR b1 = $1 OR b2 = $1 OR b3 = $1", teamNumber_).
+ Find(&matches)
+ return matches, result.Error
}
func (database *Database) QueryAllShifts(matchNumber_ int) ([]Shift, error) {
- rows, err := database.Query("SELECT * FROM shift_schedule WHERE MatchNumber = $1", matchNumber_)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to select from shift for team: ", err))
- }
- defer rows.Close()
-
var shifts []Shift
- for rows.Next() {
- var shift Shift
- var id int
- err = rows.Scan(&id, &shift.MatchNumber,
- &shift.R1scouter, &shift.R2scouter, &shift.R3scouter, &shift.B1scouter, &shift.B2scouter, &shift.B3scouter)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to scan from matches: ", err))
- }
- shifts = append(shifts, shift)
- }
- return shifts, nil
+ result := database.Where("match_number = ?", matchNumber_).Find(&shifts)
+ return shifts, result.Error
}
func (database *Database) QueryStats(teamNumber_ int) ([]Stats, error) {
- rows, err := database.Query("SELECT * FROM team_match_stats WHERE TeamNumber = $1", teamNumber_)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to select from stats: ", err))
+ var stats []Stats
+ result := database.Where("team_number = ?", teamNumber_).Find(&stats)
+ // Pack the auto balls array.
+ for i := range stats {
+ packStats(&stats[i])
}
- defer rows.Close()
-
- var teams []Stats
- for rows.Next() {
- var team Stats
- err = rows.Scan(
- &team.TeamNumber, &team.MatchNumber, &team.SetNumber, &team.CompLevel,
- &team.StartingQuadrant,
- &team.AutoBallPickedUp[0], &team.AutoBallPickedUp[1], &team.AutoBallPickedUp[2],
- &team.AutoBallPickedUp[3], &team.AutoBallPickedUp[4],
- &team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots,
- &team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto,
- &team.PlayedDefense, &team.DefenseReceivedScore, &team.Climbing,
- &team.Comment, &team.CollectedBy)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err))
- }
- teams = append(teams, team)
- }
- return teams, nil
+ return stats, result.Error
}
-func (database *Database) QueryNotes(TeamNumber int32) (NotesData, error) {
- rows, err := database.Query("SELECT * FROM team_notes WHERE TeamNumber = $1", TeamNumber)
- if err != nil {
- return NotesData{}, errors.New(fmt.Sprint("Failed to select from notes: ", err))
+func (database *Database) QueryNotes(TeamNumber int32) ([]string, error) {
+ var rawNotes []NotesData
+ result := database.Where("team_number = ?", TeamNumber).Find(&rawNotes)
+ if result.Error != nil {
+ return nil, result.Error
}
- defer rows.Close()
- var notes []string
- for rows.Next() {
- var id int32
- var data string
- err = rows.Scan(&id, &TeamNumber, &data)
- if err != nil {
- return NotesData{}, errors.New(fmt.Sprint("Failed to scan from notes: ", err))
- }
- notes = append(notes, data)
+ notes := make([]string, len(rawNotes))
+ for i := range rawNotes {
+ notes[i] = rawNotes[i].Notes
}
- return NotesData{TeamNumber, notes}, nil
+ return notes, nil
}
func (database *Database) QueryRankings(TeamNumber int) ([]Ranking, error) {
- rows, err := database.Query("SELECT * FROM rankings WHERE TeamNumber = $1", TeamNumber)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to select from rankings: ", err))
- }
- defer rows.Close()
-
- all_rankings := make([]Ranking, 0)
- for rows.Next() {
- var ranking Ranking
- var id int
- err = rows.Scan(&id,
- &ranking.Losses, &ranking.Wins, &ranking.Ties,
- &ranking.Rank, &ranking.Dq, &ranking.TeamNumber)
- if err != nil {
- return nil, errors.New(fmt.Sprint("Failed to scan from rankings: ", err))
- }
- all_rankings = append(all_rankings, ranking)
- }
- return all_rankings, nil
+ var rankins []Ranking
+ result := database.Where("team_number = ?", TeamNumber).Find(&rankins)
+ return rankins, result.Error
}
func (database *Database) AddNotes(data NotesData) error {
- if len(data.Notes) > 1 {
- return errors.New("Can only insert one row of notes at a time")
- }
- statement, err := database.Prepare("INSERT INTO " +
- "team_notes(TeamNumber, Notes)" +
- "VALUES ($1, $2)")
- if err != nil {
- return errors.New(fmt.Sprint("Failed to prepare insertion into notes table: ", err))
- }
- defer statement.Close()
+ result := database.Create(&NotesData{
+ TeamNumber: data.TeamNumber,
+ Notes: data.Notes,
+ GoodDriving: data.GoodDriving,
+ BadDriving: data.BadDriving,
+ SketchyClimb: data.SketchyClimb,
+ SolidClimb: data.SolidClimb,
+ GoodDefense: data.GoodDefense,
+ BadDefense: data.BadDefense,
+ })
+ return result.Error
+}
- _, err = statement.Exec(data.TeamNumber, data.Notes[0])
- if err != nil {
- return errors.New(fmt.Sprint("Failed to insert into Notes database: ", err))
- }
- return nil
+func (database *Database) AddDriverRanking(data DriverRankingData) error {
+ result := database.Create(&DriverRankingData{
+ MatchNumber: data.MatchNumber,
+ Rank1: data.Rank1,
+ Rank2: data.Rank2,
+ Rank3: data.Rank3,
+ })
+ return result.Error
+}
+
+func (database *Database) QueryDriverRanking(MatchNumber int) ([]DriverRankingData, error) {
+ var data []DriverRankingData
+ result := database.Where("match_number = ?", MatchNumber).Find(&data)
+ return data, result.Error
}
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index 438e52e..460b177 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -9,6 +9,8 @@
"strings"
"testing"
"time"
+
+ "github.com/davecgh/go-spew/spew"
)
// Shortcut for error checking. If the specified error is non-nil, print the
@@ -26,7 +28,6 @@
func (fixture dbFixture) TearDown() {
fixture.db.Delete()
- fixture.db.Close()
log.Println("Shutting down testdb")
fixture.server.Process.Signal(os.Interrupt)
fixture.server.Process.Wait()
@@ -55,9 +56,17 @@
}
log.Println("Connected to postgres.")
+ fixture.db.SetDebugLogLevel()
+
return fixture
}
+func checkDeepEqual(t *testing.T, expected interface{}, actual interface{}) {
+ if !reflect.DeepEqual(expected, actual) {
+ t.Fatalf(spew.Sprintf("Got %#v,\nbut expected %#v.", actual, expected))
+ }
+}
+
func TestAddToMatchDB(t *testing.T) {
fixture := createDatabase(t)
defer fixture.TearDown()
@@ -77,9 +86,7 @@
got, err := fixture.db.ReturnMatches()
check(t, err, "Failed ReturnMatches()")
- if !reflect.DeepEqual(correct, got) {
- t.Fatalf("Got %#v,\nbut expected %#v.", got, correct)
- }
+ checkDeepEqual(t, correct, got)
}
func TestAddOrUpdateRankingsDB(t *testing.T) {
@@ -198,6 +205,7 @@
stats := Stats{
TeamNumber: 1236, MatchNumber: 7,
+ SetNumber: 1, CompLevel: "qual",
StartingQuadrant: 2,
AutoBallPickedUp: [5]bool{false, false, false, true, false},
ShotsMissed: 9, UpperGoalShots: 5, LowerGoalShots: 4,
@@ -698,25 +706,20 @@
got, err := fixture.db.QueryRankings(125)
check(t, err, "Failed QueryRankings()")
- if !reflect.DeepEqual(correct, got) {
- t.Errorf("Got %#v,\nbut expected %#v.", got, correct)
- }
+ checkDeepEqual(t, correct, got)
}
func TestNotes(t *testing.T) {
fixture := createDatabase(t)
defer fixture.TearDown()
- expected := NotesData{
- TeamNumber: 1234,
- Notes: []string{"Note 1", "Note 3"},
- }
+ expected := []string{"Note 1", "Note 3"}
- err := fixture.db.AddNotes(NotesData{1234, []string{"Note 1"}})
+ err := fixture.db.AddNotes(NotesData{TeamNumber: 1234, Notes: "Note 1", GoodDriving: true, BadDriving: false, SketchyClimb: false, SolidClimb: true, GoodDefense: false, BadDefense: true})
check(t, err, "Failed to add Note")
- err = fixture.db.AddNotes(NotesData{1235, []string{"Note 2"}})
+ err = fixture.db.AddNotes(NotesData{TeamNumber: 1235, Notes: "Note 2", GoodDriving: false, BadDriving: true, SketchyClimb: false, SolidClimb: true, GoodDefense: false, BadDefense: false})
check(t, err, "Failed to add Note")
- err = fixture.db.AddNotes(NotesData{1234, []string{"Note 3"}})
+ err = fixture.db.AddNotes(NotesData{TeamNumber: 1234, Notes: "Note 3", GoodDriving: true, BadDriving: false, SketchyClimb: false, SolidClimb: true, GoodDefense: true, BadDefense: false})
check(t, err, "Failed to add Note")
actual, err := fixture.db.QueryNotes(1234)
@@ -726,3 +729,33 @@
t.Errorf("Got %#v,\nbut expected %#v.", actual, expected)
}
}
+
+func TestDriverRanking(t *testing.T) {
+ fixture := createDatabase(t)
+ defer fixture.TearDown()
+
+ expected := []DriverRankingData{
+ {ID: 1, MatchNumber: 12, Rank1: 1234, Rank2: 1235, Rank3: 1236},
+ {ID: 2, MatchNumber: 12, Rank1: 1236, Rank2: 1235, Rank3: 1234},
+ }
+
+ err := fixture.db.AddDriverRanking(
+ DriverRankingData{MatchNumber: 12, Rank1: 1234, Rank2: 1235, Rank3: 1236},
+ )
+ check(t, err, "Failed to add Driver Ranking")
+ err = fixture.db.AddDriverRanking(
+ DriverRankingData{MatchNumber: 12, Rank1: 1236, Rank2: 1235, Rank3: 1234},
+ )
+ check(t, err, "Failed to add Driver Ranking")
+ err = fixture.db.AddDriverRanking(
+ DriverRankingData{MatchNumber: 13, Rank1: 1235, Rank2: 1234, Rank3: 1236},
+ )
+ check(t, err, "Failed to add Driver Ranking")
+
+ actual, err := fixture.db.QueryDriverRanking(12)
+ check(t, err, "Failed to get Driver Ranking")
+
+ if !reflect.DeepEqual(expected, actual) {
+ t.Errorf("Got %#v,\nbut expected %#v.", actual, expected)
+ }
+}
diff --git a/scouting/scouting_test.ts b/scouting/scouting_test.ts
index a34d98c..8623fa1 100644
--- a/scouting/scouting_test.ts
+++ b/scouting/scouting_test.ts
@@ -241,7 +241,6 @@
expect(await getHeadingText()).toEqual('Climb');
await element(by.id('high')).click();
- await setTextboxByIdTo('comment', 'A very useful comment here.');
await element(by.buttonText('Next')).click();
expect(await getHeadingText()).toEqual('Other');
@@ -249,6 +248,7 @@
await adjustNthSliderBy(1, 1);
await element(by.id('no_show')).click();
await element(by.id('mechanically_broke')).click();
+ await setTextboxByIdTo('comment', 'A very useful comment here.');
await element(by.buttonText('Next')).click();
expect(await getHeadingText()).toEqual('Review and Submit');
@@ -273,7 +273,6 @@
// Validate Climb.
await expectReviewFieldToBe('Climb Level', 'High');
- await expectReviewFieldToBe('Comments', 'A very useful comment here.');
// Validate Other.
await expectReviewFieldToBe('Defense Played On Rating', '3');
@@ -282,6 +281,7 @@
await expectReviewFieldToBe('Never moved', 'false');
await expectReviewFieldToBe('Battery died', 'false');
await expectReviewFieldToBe('Broke (mechanically)', 'true');
+ await expectReviewFieldToBe('Comments', 'A very useful comment here.');
await element(by.buttonText('Submit')).click();
await browser.wait(
@@ -322,4 +322,108 @@
await element(by.buttonText('Flip')).click();
}
});
+
+ it('should: submit note scouting for multiple teams', async () => {
+ // Navigate to Notes Page.
+ await loadPage();
+ await element(by.cssContainingText('.nav-link', 'Notes')).click();
+ expect(await element(by.id('page-title')).getText()).toEqual('Notes');
+
+ // Add first team.
+ await setTextboxByIdTo('team_number_notes', '1234');
+ await element(by.buttonText('Select')).click();
+
+ // Add note and select keyword for first team.
+ expect(await element(by.id('team-key-1')).getText()).toEqual('1234');
+ await element(by.id('text-input-1')).sendKeys('Good Driving');
+ await element(by.id('Good Driving_0')).click();
+
+ // Navigate to add team selection and add another team.
+ await element(by.id('add-team-button')).click();
+ await setTextboxByIdTo('team_number_notes', '1235');
+ await element(by.buttonText('Select')).click();
+
+ // Add note and select keyword for second team.
+ expect(await element(by.id('team-key-2')).getText()).toEqual('1235');
+ await element(by.id('text-input-2')).sendKeys('Bad Driving');
+ await element(by.id('Bad Driving_1')).click();
+
+ // Submit Notes.
+ await element(by.buttonText('Submit')).click();
+ expect(await element(by.id('team_number_label')).getText()).toEqual(
+ 'Team Number'
+ );
+ });
+
+ it('should: switch note text boxes with keyboard shortcuts', async () => {
+ // Navigate to Notes Page.
+ await loadPage();
+ await element(by.cssContainingText('.nav-link', 'Notes')).click();
+ expect(await element(by.id('page-title')).getText()).toEqual('Notes');
+
+ // Add first team.
+ await setTextboxByIdTo('team_number_notes', '1234');
+ await element(by.buttonText('Select')).click();
+
+ // Add second team.
+ await element(by.id('add-team-button')).click();
+ await setTextboxByIdTo('team_number_notes', '1235');
+ await element(by.buttonText('Select')).click();
+
+ // Add third team.
+ await element(by.id('add-team-button')).click();
+ await setTextboxByIdTo('team_number_notes', '1236');
+ await element(by.buttonText('Select')).click();
+
+ for (let i = 1; i <= 3; i++) {
+ // Press Control + i
+ // Keyup Control for future actions.
+ browser
+ .actions()
+ .keyDown(protractor.Key.CONTROL)
+ .sendKeys(i.toString())
+ .keyUp(protractor.Key.CONTROL)
+ .perform();
+
+ // Expect text input to be focused.
+ expect(
+ await browser.driver.switchTo().activeElement().getAttribute('id')
+ ).toEqual('text-input-' + i);
+ }
+ });
+ it('should: submit driver ranking', async () => {
+ // Navigate to Driver Ranking Page.
+ await loadPage();
+ await element(by.cssContainingText('.nav-link', 'Driver Ranking')).click();
+ expect(await element(by.id('page-title')).getText()).toEqual(
+ 'Driver Ranking'
+ );
+
+ // Input match and team numbers.
+ await setTextboxByIdTo('match_number_selection', '11');
+ await setTextboxByIdTo('team_input_0', '123');
+ await setTextboxByIdTo('team_input_1', '456');
+ await setTextboxByIdTo('team_input_2', '789');
+ await element(by.id('select_button')).click();
+
+ // Verify match and team key input.
+ expect(await element(by.id('match_number_heading')).getText()).toEqual(
+ 'Match #11'
+ );
+ expect(await element(by.id('team_key_label_0')).getText()).toEqual('123');
+ expect(await element(by.id('team_key_label_1')).getText()).toEqual('456');
+ expect(await element(by.id('team_key_label_2')).getText()).toEqual('789');
+
+ // Rank teams.
+ await element(by.id('up_button_2')).click();
+ await element(by.id('down_button_0')).click();
+
+ // Verify ranking change.
+ expect(await element(by.id('team_key_label_0')).getText()).toEqual('789');
+ expect(await element(by.id('team_key_label_1')).getText()).toEqual('123');
+ expect(await element(by.id('team_key_label_2')).getText()).toEqual('456');
+
+ // Submit.
+ await element(by.id('submit_button')).click();
+ });
});
diff --git a/scouting/webserver/main.go b/scouting/webserver/main.go
index 5d4ab01..d2fbdfe 100644
--- a/scouting/webserver/main.go
+++ b/scouting/webserver/main.go
@@ -117,7 +117,7 @@
if err != nil {
log.Fatal("Failed to connect to database: ", err)
}
- defer database.Close()
+ defer database.Delete()
scrapeMatchList := func(year int32, eventCode string) ([]scraping.Match, error) {
if *blueAllianceConfigPtr == "" {
diff --git a/scouting/webserver/requests/BUILD b/scouting/webserver/requests/BUILD
index 87575a9..5c9ede4 100644
--- a/scouting/webserver/requests/BUILD
+++ b/scouting/webserver/requests/BUILD
@@ -24,6 +24,8 @@
"//scouting/webserver/requests/messages:request_shift_schedule_response_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
+ "//scouting/webserver/requests/messages:submit_driver_ranking_go_fbs",
+ "//scouting/webserver/requests/messages:submit_driver_ranking_response_go_fbs",
"//scouting/webserver/requests/messages:submit_notes_go_fbs",
"//scouting/webserver/requests/messages:submit_notes_response_go_fbs",
"//scouting/webserver/requests/messages:submit_shift_schedule_go_fbs",
@@ -56,6 +58,7 @@
"//scouting/webserver/requests/messages:request_shift_schedule_response_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
+ "//scouting/webserver/requests/messages:submit_driver_ranking_go_fbs",
"//scouting/webserver/requests/messages:submit_notes_go_fbs",
"//scouting/webserver/requests/messages:submit_shift_schedule_go_fbs",
"//scouting/webserver/server",
diff --git a/scouting/webserver/requests/debug/BUILD b/scouting/webserver/requests/debug/BUILD
index 04c4ffa..f826831 100644
--- a/scouting/webserver/requests/debug/BUILD
+++ b/scouting/webserver/requests/debug/BUILD
@@ -15,6 +15,7 @@
"//scouting/webserver/requests/messages:request_notes_for_team_response_go_fbs",
"//scouting/webserver/requests/messages:request_shift_schedule_response_go_fbs",
"//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
+ "//scouting/webserver/requests/messages:submit_driver_ranking_response_go_fbs",
"//scouting/webserver/requests/messages:submit_notes_response_go_fbs",
"//scouting/webserver/requests/messages:submit_shift_schedule_response_go_fbs",
"@com_github_google_flatbuffers//go:go_default_library",
diff --git a/scouting/webserver/requests/debug/debug.go b/scouting/webserver/requests/debug/debug.go
index b3df518..fc0896c 100644
--- a/scouting/webserver/requests/debug/debug.go
+++ b/scouting/webserver/requests/debug/debug.go
@@ -17,6 +17,7 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
flatbuffers "github.com/google/flatbuffers/go"
@@ -157,3 +158,9 @@
server+"/requests/submit/shift_schedule", requestBytes,
submit_shift_schedule_response.GetRootAsSubmitShiftScheduleResponse)
}
+
+func SubmitDriverRanking(server string, requestBytes []byte) (*submit_driver_ranking_response.SubmitDriverRankingResponseT, error) {
+ return sendMessage[submit_driver_ranking_response.SubmitDriverRankingResponseT](
+ server+"/requests/submit/submit_driver_ranking", requestBytes,
+ submit_driver_ranking_response.GetRootAsSubmitDriverRankingResponse)
+}
diff --git a/scouting/webserver/requests/messages/BUILD b/scouting/webserver/requests/messages/BUILD
index b2d21a2..c14a857 100644
--- a/scouting/webserver/requests/messages/BUILD
+++ b/scouting/webserver/requests/messages/BUILD
@@ -21,6 +21,8 @@
"request_shift_schedule_response",
"submit_shift_schedule",
"submit_shift_schedule_response",
+ "submit_driver_ranking",
+ "submit_driver_ranking_response",
)
filegroup(
diff --git a/scouting/webserver/requests/messages/submit_driver_ranking.fbs b/scouting/webserver/requests/messages/submit_driver_ranking.fbs
new file mode 100644
index 0000000..ac1e218
--- /dev/null
+++ b/scouting/webserver/requests/messages/submit_driver_ranking.fbs
@@ -0,0 +1,10 @@
+namespace scouting.webserver.requests;
+
+table SubmitDriverRanking {
+ matchNumber:int (id: 0);
+ rank1:int (id: 1);
+ rank2:int (id: 2);
+ rank3:int (id: 3);
+}
+
+root_type SubmitDriverRanking;
diff --git a/scouting/webserver/requests/messages/submit_driver_ranking_response.fbs b/scouting/webserver/requests/messages/submit_driver_ranking_response.fbs
new file mode 100644
index 0000000..78c6445
--- /dev/null
+++ b/scouting/webserver/requests/messages/submit_driver_ranking_response.fbs
@@ -0,0 +1,8 @@
+namespace scouting.webserver.requests;
+
+table SubmitDriverRankingResponse {
+ // empty response
+}
+
+root_type SubmitDriverRankingResponse;
+
diff --git a/scouting/webserver/requests/messages/submit_notes.fbs b/scouting/webserver/requests/messages/submit_notes.fbs
index cf111b3..1498e26 100644
--- a/scouting/webserver/requests/messages/submit_notes.fbs
+++ b/scouting/webserver/requests/messages/submit_notes.fbs
@@ -3,6 +3,12 @@
table SubmitNotes {
team:int (id: 0);
notes:string (id: 1);
+ good_driving:bool (id: 2);
+ bad_driving:bool (id: 3);
+ sketchy_climb:bool (id: 4);
+ solid_climb:bool (id: 5);
+ good_defense:bool (id: 6);
+ bad_defense:bool (id: 7);
}
root_type SubmitNotes;
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 67a1722..12bc3da 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -27,6 +27,8 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
@@ -53,6 +55,8 @@
type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
+type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
+type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
// The interface we expect the database abstraction to conform to.
// We use an interface here because it makes unit testing easier.
@@ -66,8 +70,9 @@
QueryMatches(int32) ([]db.Match, error)
QueryAllShifts(int) ([]db.Shift, error)
QueryStats(int) ([]db.Stats, error)
- QueryNotes(int32) (db.NotesData, error)
+ QueryNotes(int32) ([]string, error)
AddNotes(db.NotesData) error
+ AddDriverRanking(db.DriverRankingData) error
}
type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
@@ -337,7 +342,33 @@
func parseTeamKey(teamKey string) (int, error) {
// TBA prefixes teams with "frc". Not sure why. Get rid of that.
teamKey = strings.TrimPrefix(teamKey, "frc")
- return strconv.Atoi(teamKey)
+ magnitude := 0
+ if strings.HasSuffix(teamKey, "A") {
+ magnitude = 0
+ teamKey = strings.TrimSuffix(teamKey, "A")
+ } else if strings.HasSuffix(teamKey, "B") {
+ magnitude = 9
+ teamKey = strings.TrimSuffix(teamKey, "B")
+ } else if strings.HasSuffix(teamKey, "C") {
+ magnitude = 8
+ teamKey = strings.TrimSuffix(teamKey, "C")
+ } else if strings.HasSuffix(teamKey, "D") {
+ magnitude = 7
+ teamKey = strings.TrimSuffix(teamKey, "D")
+ } else if strings.HasSuffix(teamKey, "E") {
+ magnitude = 6
+ teamKey = strings.TrimSuffix(teamKey, "E")
+ } else if strings.HasSuffix(teamKey, "F") {
+ magnitude = 5
+ teamKey = strings.TrimSuffix(teamKey, "F")
+ }
+
+ if magnitude != 0 {
+ teamKey = strconv.Itoa(magnitude) + teamKey
+ }
+
+ result, err := strconv.Atoi(teamKey)
+ return result, err
}
// Parses the alliance data from the specified match and returns the three red
@@ -445,8 +476,14 @@
}
err = handler.db.AddNotes(db.NotesData{
- TeamNumber: request.Team(),
- Notes: []string{string(request.Notes())},
+ TeamNumber: request.Team(),
+ Notes: string(request.Notes()),
+ GoodDriving: bool(request.GoodDriving()),
+ BadDriving: bool(request.BadDriving()),
+ SketchyClimb: bool(request.SketchyClimb()),
+ SolidClimb: bool(request.SolidClimb()),
+ GoodDefense: bool(request.GoodDefense()),
+ BadDefense: bool(request.BadDefense()),
})
if err != nil {
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
@@ -475,14 +512,14 @@
return
}
- notesData, err := handler.db.QueryNotes(request.Team())
+ notes, err := handler.db.QueryNotes(request.Team())
if err != nil {
respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
return
}
var response RequestNotesForTeamResponseT
- for _, data := range notesData.Notes {
+ for _, data := range notes {
response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
}
@@ -576,6 +613,40 @@
w.Write(builder.FinishedBytes())
}
+type SubmitDriverRankingHandler struct {
+ db Database
+}
+
+func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ requestBytes, err := io.ReadAll(req.Body)
+ if err != nil {
+ respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
+ return
+ }
+
+ request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
+ if !success {
+ return
+ }
+
+ err = handler.db.AddDriverRanking(db.DriverRankingData{
+ MatchNumber: request.MatchNumber(),
+ Rank1: request.Rank1(),
+ Rank2: request.Rank2(),
+ Rank3: request.Rank3(),
+ })
+
+ if err != nil {
+ respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
+ return
+ }
+
+ var response SubmitDriverRankingResponseT
+ builder := flatbuffers.NewBuilder(10)
+ builder.Finish((&response).Pack(builder))
+ w.Write(builder.FinishedBytes())
+}
+
func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
scoutingServer.HandleFunc("/requests", unknown)
scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
@@ -587,4 +658,5 @@
scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
+ scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
}
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index 44fd5db..55b789b 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -24,6 +24,7 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
+ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
"github.com/frc971/971-Robot-Code/scouting/webserver/server"
@@ -314,8 +315,14 @@
builder := flatbuffers.NewBuilder(1024)
builder.Finish((&submit_notes.SubmitNotesT{
- Team: 971,
- Notes: "Notes",
+ Team: 971,
+ Notes: "Notes",
+ GoodDriving: true,
+ BadDriving: false,
+ SketchyClimb: true,
+ SolidClimb: false,
+ GoodDefense: true,
+ BadDefense: false,
}).Pack(builder))
_, err := debug.SubmitNotes("http://localhost:8080", builder.FinishedBytes())
@@ -324,7 +331,16 @@
}
expected := []db.NotesData{
- {TeamNumber: 971, Notes: []string{"Notes"}},
+ {
+ TeamNumber: 971,
+ Notes: "Notes",
+ GoodDriving: true,
+ BadDriving: false,
+ SketchyClimb: true,
+ SolidClimb: false,
+ GoodDefense: true,
+ BadDefense: false,
+ },
}
if !reflect.DeepEqual(database.notes, expected) {
@@ -335,8 +351,14 @@
func TestRequestNotes(t *testing.T) {
database := MockDatabase{
notes: []db.NotesData{{
- TeamNumber: 971,
- Notes: []string{"Notes"},
+ TeamNumber: 971,
+ Notes: "Notes",
+ GoodDriving: true,
+ BadDriving: false,
+ SketchyClimb: true,
+ SolidClimb: false,
+ GoodDefense: true,
+ BadDefense: false,
}},
}
scoutingServer := server.NewScoutingServer()
@@ -539,14 +561,44 @@
}
}
+func TestSubmitDriverRanking(t *testing.T) {
+ database := MockDatabase{}
+ scoutingServer := server.NewScoutingServer()
+ HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
+ scoutingServer.Start(8080)
+ defer scoutingServer.Stop()
+
+ builder := flatbuffers.NewBuilder(1024)
+ builder.Finish((&submit_driver_ranking.SubmitDriverRankingT{
+ MatchNumber: 36,
+ Rank1: 1234,
+ Rank2: 1235,
+ Rank3: 1236,
+ }).Pack(builder))
+
+ _, err := debug.SubmitDriverRanking("http://localhost:8080", builder.FinishedBytes())
+ if err != nil {
+ t.Fatal("Failed to submit driver ranking: ", err)
+ }
+
+ expected := []db.DriverRankingData{
+ {MatchNumber: 36, Rank1: 1234, Rank2: 1235, Rank3: 1236},
+ }
+
+ if !reflect.DeepEqual(database.driver_ranking, expected) {
+ t.Fatal("Submitted notes did not match", expected, database.notes)
+ }
+}
+
// A mocked database we can use for testing. Add functionality to this as
// needed for your tests.
type MockDatabase struct {
- matches []db.Match
- stats []db.Stats
- notes []db.NotesData
- shiftSchedule []db.Shift
+ matches []db.Match
+ stats []db.Stats
+ notes []db.NotesData
+ shiftSchedule []db.Shift
+ driver_ranking []db.DriverRankingData
}
func (database *MockDatabase) AddToMatch(match db.Match) error {
@@ -584,14 +636,14 @@
return []db.Stats{}, nil
}
-func (database *MockDatabase) QueryNotes(requestedTeam int32) (db.NotesData, error) {
+func (database *MockDatabase) QueryNotes(requestedTeam int32) ([]string, error) {
var results []string
for _, data := range database.notes {
if data.TeamNumber == requestedTeam {
- results = append(results, data.Notes[0])
+ results = append(results, data.Notes)
}
}
- return db.NotesData{TeamNumber: requestedTeam, Notes: results}, nil
+ return results, nil
}
func (database *MockDatabase) AddNotes(data db.NotesData) error {
@@ -612,6 +664,11 @@
return []db.Shift{}, nil
}
+func (database *MockDatabase) AddDriverRanking(data db.DriverRankingData) error {
+ database.driver_ranking = append(database.driver_ranking, data)
+ return nil
+}
+
// Returns an empty match list from the fake The Blue Alliance scraping.
func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
return nil, nil
diff --git a/scouting/www/BUILD b/scouting/www/BUILD
index 0b7cebb..ee0659b 100644
--- a/scouting/www/BUILD
+++ b/scouting/www/BUILD
@@ -16,6 +16,7 @@
use_angular_plugin = True,
visibility = ["//visibility:public"],
deps = [
+ "//scouting/www/driver_ranking",
"//scouting/www/entry",
"//scouting/www/import_match_list",
"//scouting/www/match_list",
diff --git a/scouting/www/app.ng.html b/scouting/www/app.ng.html
index 297fd39..d9dbead 100644
--- a/scouting/www/app.ng.html
+++ b/scouting/www/app.ng.html
@@ -40,6 +40,15 @@
<li class="nav-item">
<a
class="nav-link"
+ [class.active]="tabIs('DriverRanking')"
+ (click)="switchTabToGuarded('DriverRanking')"
+ >
+ Driver Ranking
+ </a>
+ </li>
+ <li class="nav-item">
+ <a
+ class="nav-link"
[class.active]="tabIs('ImportMatchList')"
(click)="switchTabToGuarded('ImportMatchList')"
>
@@ -80,6 +89,7 @@
*ngSwitchCase="'Entry'"
></app-entry>
<frc971-notes *ngSwitchCase="'Notes'"></frc971-notes>
+ <app-driver-ranking *ngSwitchCase="'DriverRanking'"></app-driver-ranking>
<app-import-match-list
*ngSwitchCase="'ImportMatchList'"
></app-import-match-list>
diff --git a/scouting/www/app.ts b/scouting/www/app.ts
index 4f95c90..b26f815 100644
--- a/scouting/www/app.ts
+++ b/scouting/www/app.ts
@@ -4,6 +4,7 @@
| 'MatchList'
| 'Notes'
| 'Entry'
+ | 'DriverRanking'
| 'ImportMatchList'
| 'ShiftSchedule'
| 'View';
diff --git a/scouting/www/app_module.ts b/scouting/www/app_module.ts
index 8c18f7a..04d72b3 100644
--- a/scouting/www/app_module.ts
+++ b/scouting/www/app_module.ts
@@ -9,6 +9,7 @@
import {NotesModule} from './notes/notes.module';
import {ShiftScheduleModule} from './shift_schedule/shift_schedule.module';
import {ViewModule} from './view/view.module';
+import {DriverRankingModule} from './driver_ranking/driver_ranking.module';
@NgModule({
declarations: [App],
@@ -20,6 +21,7 @@
ImportMatchListModule,
MatchListModule,
ShiftScheduleModule,
+ DriverRankingModule,
ViewModule,
],
exports: [App],
diff --git a/scouting/www/driver_ranking/BUILD b/scouting/www/driver_ranking/BUILD
new file mode 100644
index 0000000..10b6f99
--- /dev/null
+++ b/scouting/www/driver_ranking/BUILD
@@ -0,0 +1,26 @@
+load("@npm//@bazel/typescript:index.bzl", "ts_library")
+
+ts_library(
+ name = "driver_ranking",
+ srcs = [
+ "driver_ranking.component.ts",
+ "driver_ranking.module.ts",
+ ],
+ angular_assets = [
+ "driver_ranking.component.css",
+ "driver_ranking.ng.html",
+ "//scouting/www:common_css",
+ ],
+ compiler = "//tools:tsc_wrapped_with_angular",
+ target_compatible_with = ["@platforms//cpu:x86_64"],
+ use_angular_plugin = True,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//scouting/webserver/requests/messages:error_response_ts_fbs",
+ "//scouting/webserver/requests/messages:submit_driver_ranking_ts_fbs",
+ "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+ "@npm//@angular/common",
+ "@npm//@angular/core",
+ "@npm//@angular/forms",
+ ],
+)
diff --git a/scouting/www/driver_ranking/driver_ranking.component.css b/scouting/www/driver_ranking/driver_ranking.component.css
new file mode 100644
index 0000000..e220645
--- /dev/null
+++ b/scouting/www/driver_ranking/driver_ranking.component.css
@@ -0,0 +1,3 @@
+* {
+ padding: 10px;
+}
diff --git a/scouting/www/driver_ranking/driver_ranking.component.ts b/scouting/www/driver_ranking/driver_ranking.component.ts
new file mode 100644
index 0000000..aadb3b0
--- /dev/null
+++ b/scouting/www/driver_ranking/driver_ranking.component.ts
@@ -0,0 +1,93 @@
+import {Component, OnInit} from '@angular/core';
+import {Builder, ByteBuffer} from 'flatbuffers';
+import {SubmitDriverRanking} from 'org_frc971/scouting/webserver/requests/messages/submit_driver_ranking_generated';
+import {ErrorResponse} from 'org_frc971/scouting/webserver/requests/messages/error_response_generated';
+
+// TeamSelection: Display form to input which
+// teams to rank and the match number.
+// Data: Display the ranking interface where
+// the scout can reorder teams and submit data.
+type Section = 'TeamSelection' | 'Data';
+
+@Component({
+ selector: 'app-driver-ranking',
+ templateUrl: './driver_ranking.ng.html',
+ styleUrls: ['../common.css', './driver_ranking.component.css'],
+})
+export class DriverRankingComponent {
+ section: Section = 'TeamSelection';
+
+ // Stores the team keys and rank (order of the array).
+ team_ranking: number[] = [971, 972, 973];
+
+ match_number: number = 1;
+
+ errorMessage = '';
+
+ setTeamNumbers() {
+ this.section = 'Data';
+ }
+
+ rankUp(index: number) {
+ if (index > 0) {
+ this.changeRank(index, index - 1);
+ }
+ }
+
+ rankDown(index: number) {
+ if (index < 2) {
+ this.changeRank(index, index + 1);
+ }
+ }
+
+ // Change the rank of a team in team_ranking.
+ // Move the the team at index 'fromIndex'
+ // to the index 'toIndex'.
+ // Ex. Moving the rank 2 (index 1) team to rank1 (index 0)
+ // would be changeRank(1, 0)
+
+ changeRank(fromIndex: number, toIndex: number) {
+ var element = this.team_ranking[fromIndex];
+ this.team_ranking.splice(fromIndex, 1);
+ this.team_ranking.splice(toIndex, 0, element);
+ }
+
+ editTeams() {
+ this.section = 'TeamSelection';
+ }
+
+ async submitData() {
+ const builder = new Builder();
+ builder.finish(
+ SubmitDriverRanking.createSubmitDriverRanking(
+ builder,
+ this.match_number,
+ this.team_ranking[0],
+ this.team_ranking[1],
+ this.team_ranking[2]
+ )
+ );
+ const buffer = builder.asUint8Array();
+ const res = await fetch('/requests/submit/submit_driver_ranking', {
+ method: 'POST',
+ body: buffer,
+ });
+
+ if (!res.ok) {
+ const resBuffer = await res.arrayBuffer();
+ const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
+ const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
+
+ const errorMessage = parsedResponse.errorMessage();
+ this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
+ return;
+ }
+
+ // Increment the match number.
+ this.match_number = this.match_number + 1;
+
+ // Reset Data.
+ this.section = 'TeamSelection';
+ this.team_ranking = [971, 972, 973];
+ }
+}
diff --git a/scouting/www/driver_ranking/driver_ranking.module.ts b/scouting/www/driver_ranking/driver_ranking.module.ts
new file mode 100644
index 0000000..7fe3623
--- /dev/null
+++ b/scouting/www/driver_ranking/driver_ranking.module.ts
@@ -0,0 +1,11 @@
+import {CommonModule} from '@angular/common';
+import {NgModule} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import {DriverRankingComponent} from './driver_ranking.component';
+
+@NgModule({
+ declarations: [DriverRankingComponent],
+ exports: [DriverRankingComponent],
+ imports: [CommonModule, FormsModule],
+})
+export class DriverRankingModule {}
diff --git a/scouting/www/driver_ranking/driver_ranking.ng.html b/scouting/www/driver_ranking/driver_ranking.ng.html
new file mode 100644
index 0000000..452359c
--- /dev/null
+++ b/scouting/www/driver_ranking/driver_ranking.ng.html
@@ -0,0 +1,80 @@
+<h2 id="page-title">Driver Ranking</h2>
+
+<ng-container [ngSwitch]="section">
+ <div *ngSwitchCase="'TeamSelection'">
+ <label for="match_number_selection">Match Number</label>
+ <input
+ [(ngModel)]="match_number"
+ type="number"
+ id="match_number_selection"
+ min="1"
+ max="9999"
+ />
+ <br />
+ <br />
+ <label>Team Numbers</label>
+ <input
+ *ngFor="let x of [1,2,3]; let i = index;"
+ [(ngModel)]="team_ranking[i]"
+ type="number"
+ min="1"
+ max="9999"
+ id="team_input_{{i}}"
+ />
+ <button
+ class="btn btn-primary"
+ (click)="setTeamNumbers()"
+ id="select_button"
+ >
+ Select
+ </button>
+ </div>
+ <div *ngSwitchCase="'Data'">
+ <h4 id="match_number_heading">Match #{{match_number}}</h4>
+ <div *ngFor="let team_key of team_ranking; let i = index">
+ <div class="d-flex flex-row justify-content-center pt-2">
+ <div class="d-flex flex-row">
+ <h4 class="align-self-center" id="team_rank_label_{{i}}">
+ {{i + 1}}
+ </h4>
+ <h1 class="align-self-center" id="team_key_label_{{i}}">
+ {{team_key}}
+ </h1>
+ </div>
+ <button
+ class="btn btn-success"
+ (click)="rankUp(i)"
+ id="up_button_{{i}}"
+ >
+ ↑
+ </button>
+ <!--↑ is the html code for an up arrow-->
+ <button
+ class="btn btn-danger"
+ (click)="rankDown(i)"
+ id="down_button_{{i}}"
+ >
+ ↓
+ </button>
+ <!--↓ is the html code for a down arrow-->
+ </div>
+ </div>
+ <div class="d-flex flex-row justify-content-center pt-2">
+ <div>
+ <button class="btn btn-secondary" (click)="editTeams()">
+ Edit Teams
+ </button>
+ </div>
+ <div>
+ <button
+ class="btn btn-success"
+ (click)="submitData()"
+ id="submit_button"
+ >
+ Submit
+ </button>
+ </div>
+ </div>
+ </div>
+ <div class="error">{{errorMessage}}</div>
+</ng-container>
diff --git a/scouting/www/entry/entry.component.css b/scouting/www/entry/entry.component.css
index 6d13657..a78a00a 100644
--- a/scouting/www/entry/entry.component.css
+++ b/scouting/www/entry/entry.component.css
@@ -8,8 +8,8 @@
}
textarea {
- width: 300px;
- height: 150px;
+ width: 350px;
+ height: 180px;
}
button {
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index 9be8db7..e73cfb0 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -254,10 +254,6 @@
</label>
<br />
</form>
- <div class="row">
- <h4>Comments</h4>
- <textarea [(ngModel)]="comment" id="comment"></textarea>
- </div>
<div class="buttons">
<button class="btn btn-primary" (click)="prevSection()">Back</button>
<button class="btn btn-primary" (click)="nextSection()">Next</button>
@@ -360,6 +356,15 @@
</form>
</div>
+ <div class="row">
+ <h4>General Comments About Match</h4>
+ <textarea
+ [(ngModel)]="comment"
+ id="comment"
+ placeholder="optional"
+ ></textarea>
+ </div>
+
<div class="buttons">
<button class="btn btn-primary" (click)="prevSection()">Back</button>
<button class="btn btn-primary" (click)="nextSection()">Next</button>
@@ -398,7 +403,6 @@
<h4>Climb</h4>
<ul>
<li>Climb Level: {{level | levelToString}}</li>
- <li>Comments: {{comment}}</li>
</ul>
<h4>Other</h4>
@@ -410,6 +414,7 @@
<li>Battery died: {{batteryDied}}</li>
<li>Broke (mechanically): {{mechanicallyBroke}}</li>
<li>Lost coms: {{lostComs}}</li>
+ <li>Comments: {{comment}}</li>
</ul>
<span class="error_message">{{ errorMessage }}</span>
diff --git a/scouting/www/notes/notes.component.css b/scouting/www/notes/notes.component.css
index 869bdab..b601507 100644
--- a/scouting/www/notes/notes.component.css
+++ b/scouting/www/notes/notes.component.css
@@ -4,9 +4,9 @@
.text-input {
width: calc(100% - 20px);
+ height: 100px;
}
-.buttons {
- display: flex;
- justify-content: space-between;
+.container-main {
+ padding-left: 20px;
}
diff --git a/scouting/www/notes/notes.component.ts b/scouting/www/notes/notes.component.ts
index 0f0eb82..f503e2d 100644
--- a/scouting/www/notes/notes.component.ts
+++ b/scouting/www/notes/notes.component.ts
@@ -1,4 +1,4 @@
-import {Component} from '@angular/core';
+import {Component, HostListener} from '@angular/core';
import {Builder, ByteBuffer} from 'flatbuffers';
import {ErrorResponse} from 'org_frc971/scouting/webserver/requests/messages/error_response_generated';
import {RequestNotesForTeam} from 'org_frc971/scouting/webserver/requests/messages/request_notes_for_team_generated';
@@ -9,88 +9,170 @@
import {SubmitNotes} from 'org_frc971/scouting/webserver/requests/messages/submit_notes_generated';
import {SubmitNotesResponse} from 'org_frc971/scouting/webserver/requests/messages/submit_notes_response_generated';
+/*
+For new games, the keywords being used will likely need to be updated.
+To update the keywords complete the following:
+ 1) Update the Keywords Interface and KEYWORD_CHECKBOX_LABELS in notes.component.ts
+ The keys of Keywords and KEYWORD_CHECKBOX_LABELS should match.
+ 2) In notes.component.ts, update the setTeamNumber() method with the new keywords.
+ 3) Add/Edit the new keywords in /scouting/webserver/requests/messages/submit_notes.fbs.
+ 4) In notes.component.ts, update the submitData() method with the newKeywords
+ so that it matches the updated flatbuffer
+ 5) In db.go, update the NotesData struct and the
+ AddNotes method with the new keywords
+ 6) In db_test.go update the TestNotes method so the test uses the keywords
+ 7) Update the submitNoteScoutingHandler in requests.go with the new keywords
+ 8) Finally, update the corresponding test in requests_test.go (TestSubmitNotes)
+
+ Note: If you change the number of keywords you might need to
+ update how they are displayed in notes.ng.html
+*/
+
+// TeamSelection: Display form to add a team to the teams being scouted.
+// Data: Display the note textbox and keyword selection form
+// for all the teams being scouted.
type Section = 'TeamSelection' | 'Data';
-interface Note {
- readonly data: string;
+// Every keyword checkbox corresponds to a boolean.
+// If the boolean is True, the checkbox is selected
+// and the note scout saw that the robot being scouted
+// displayed said property (ex. Driving really well -> goodDriving)
+interface Keywords {
+ goodDriving: boolean;
+ badDriving: boolean;
+ sketchyClimb: boolean;
+ solidClimb: boolean;
+ goodDefense: boolean;
+ badDefense: boolean;
}
+interface Input {
+ teamNumber: number;
+ notesData: string;
+ keywordsData: Keywords;
+}
+
+const KEYWORD_CHECKBOX_LABELS = {
+ goodDriving: 'Good Driving',
+ badDriving: 'Bad Driving',
+ solidClimb: 'Solid Climb',
+ sketchyClimb: 'Sketchy Climb',
+ goodDefense: 'Good Defense',
+ badDefense: 'Bad Defense',
+} as const;
+
@Component({
selector: 'frc971-notes',
templateUrl: './notes.ng.html',
styleUrls: ['../common.css', './notes.component.css'],
})
export class Notes {
+ // Re-export KEYWORD_CHECKBOX_LABELS so that we can
+ // use it in the checkbox properties.
+ readonly KEYWORD_CHECKBOX_LABELS = KEYWORD_CHECKBOX_LABELS;
+
+ // Necessary in order to iterate the keys of KEYWORD_CHECKBOX_LABELS.
+ Object = Object;
+
section: Section = 'TeamSelection';
- notes: Note[] = [];
errorMessage = '';
+ teamNumberSelection: number = 971;
- teamNumber: number = 971;
- newData = '';
+ // Data inputted by user is stored in this array.
+ // Includes the team number, notes, and keyword selection.
+ newData: Input[] = [];
- async setTeamNumber() {
- const builder = new Builder();
- RequestNotesForTeam.startRequestNotesForTeam(builder);
- RequestNotesForTeam.addTeam(builder, this.teamNumber);
- builder.finish(RequestNotesForTeam.endRequestNotesForTeam(builder));
+ // Keyboard shortcuts to switch between text areas.
+ // Listens for Ctrl + number and focuses on the
+ // corresponding textbox.
+ // More Info: https://angular.io/api/core/HostListener
- const buffer = builder.asUint8Array();
- const res = await fetch('/requests/request/notes_for_team', {
- method: 'POST',
- body: buffer,
- });
-
- const resBuffer = await res.arrayBuffer();
- const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
-
- if (res.ok) {
- this.notes = [];
- const parsedResponse =
- RequestNotesForTeamResponse.getRootAsRequestNotesForTeamResponse(
- fbBuffer
- );
- for (let i = 0; i < parsedResponse.notesLength(); i++) {
- const fbNote = parsedResponse.notes(i);
- this.notes.push({data: fbNote.data()});
+ @HostListener('window:keyup', ['$event'])
+ onEvent(event: KeyboardEvent) {
+ if (event.ctrlKey) {
+ if (event.code.includes('Digit')) {
+ this.handleFocus(event.key);
}
- this.section = 'Data';
- } else {
- const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
-
- const errorMessage = parsedResponse.errorMessage();
- this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
}
}
- changeTeam() {
+ handleFocus(digit: string) {
+ let textArea = <HTMLInputElement>(
+ document.getElementById('text-input-' + digit)
+ );
+ if (textArea != null) {
+ textArea.focus();
+ }
+ }
+
+ setTeamNumber() {
+ let data: Input = {
+ teamNumber: this.teamNumberSelection,
+ notesData: 'Auto: \nTeleop: \nEngame: ',
+ keywordsData: {
+ goodDriving: false,
+ badDriving: false,
+ solidClimb: false,
+ sketchyClimb: false,
+ goodDefense: false,
+ badDefense: false,
+ },
+ };
+
+ this.newData.push(data);
+ this.section = 'Data';
+ }
+
+ removeTeam(index: number) {
+ this.newData.splice(index, 1);
+ if (this.newData.length == 0) {
+ this.section = 'TeamSelection';
+ } else {
+ this.section = 'Data';
+ }
+ }
+
+ addTeam() {
this.section = 'TeamSelection';
}
async submitData() {
- const builder = new Builder();
- const dataFb = builder.createString(this.newData);
- builder.finish(
- SubmitNotes.createSubmitNotes(builder, this.teamNumber, dataFb)
- );
+ for (let i = 0; i < this.newData.length; i++) {
+ const builder = new Builder();
+ const dataFb = builder.createString(this.newData[i].notesData);
- const buffer = builder.asUint8Array();
- const res = await fetch('/requests/submit/submit_notes', {
- method: 'POST',
- body: buffer,
- });
+ builder.finish(
+ SubmitNotes.createSubmitNotes(
+ builder,
+ this.newData[i].teamNumber,
+ dataFb,
+ this.newData[i].keywordsData.goodDriving,
+ this.newData[i].keywordsData.badDriving,
+ this.newData[i].keywordsData.sketchyClimb,
+ this.newData[i].keywordsData.solidClimb,
+ this.newData[i].keywordsData.goodDefense,
+ this.newData[i].keywordsData.badDefense
+ )
+ );
- if (res.ok) {
- this.newData = '';
- this.errorMessage = '';
- await this.setTeamNumber();
- } else {
- const resBuffer = await res.arrayBuffer();
- const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
- const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
+ const buffer = builder.asUint8Array();
+ const res = await fetch('/requests/submit/submit_notes', {
+ method: 'POST',
+ body: buffer,
+ });
- const errorMessage = parsedResponse.errorMessage();
- this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
+ if (!res.ok) {
+ const resBuffer = await res.arrayBuffer();
+ const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
+ const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
+ const errorMessage = parsedResponse.errorMessage();
+ this.errorMessage = `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
+ }
}
+
+ this.newData = [];
+ this.errorMessage = '';
+ this.section = 'TeamSelection';
}
}
diff --git a/scouting/www/notes/notes.ng.html b/scouting/www/notes/notes.ng.html
index a69ba88..b51a7ce 100644
--- a/scouting/www/notes/notes.ng.html
+++ b/scouting/www/notes/notes.ng.html
@@ -1,10 +1,12 @@
-<h2>Notes</h2>
+<h2 id="page-title">Notes</h2>
<ng-container [ngSwitch]="section">
<div *ngSwitchCase="'TeamSelection'">
- <label for="team_number_notes">Team Number</label>
+ <label id="team_number_label" class="label" for="team_number_notes">
+ Team Number
+ </label>
<input
- [(ngModel)]="teamNumber"
+ [(ngModel)]="teamNumberSelection"
type="number"
id="team_number_notes"
min="1"
@@ -14,17 +16,107 @@
</div>
<div *ngSwitchCase="'Data'">
- <h3>Scouting team: {{teamNumber}}</h3>
- <ul *ngFor="let note of notes">
- <li class="note">{{ note.data }}</li>
- </ul>
- <textarea class="text-input" [(ngModel)]="newData"></textarea>
- <div class="buttons">
- <button class="btn btn-primary" (click)="changeTeam()">
- Change team
- </button>
- <button class="btn btn-primary" (click)="submitData()">Submit</button>
+ <div class="container-main" *ngFor="let team of newData; let i = index">
+ <div class="pt-2 pb-2">
+ <div class="d-flex flex-row">
+ <div>
+ <button
+ class="btn bg-transparent ml-10 md-5"
+ (click)="removeTeam(i)"
+ >
+ ✖
+ <!--X Symbol-->
+ </button>
+ </div>
+ <div><h3 id="team-key-{{i+1}}">{{team.teamNumber}}</h3></div>
+ </div>
+ <div class="">
+ <!--
+ Note Input Text Areas.
+ ID property is used for keyboard shorcuts to focus
+ on the corresponding text area.
+ The data-toggle and title properties are
+ used for bootstrap tooltips.
+ -->
+ <textarea
+ class="text-input"
+ id="text-input-{{i+1}}"
+ [(ngModel)]="newData[i].notesData"
+ data-toggle="tooltip"
+ title="Ctrl + {{i+1}}"
+ ></textarea>
+ </div>
+ <!--Key Word Checkboxes-->
+ <!--Row 1 (Prevent Overflow on mobile by splitting checkboxes into 2 rows)-->
+ <!--Slice KEYWORD_CHECKBOX_LABELS using https://angular.io/api/common/SlicePipe-->
+ <div class="d-flex flex-row justify-content-around">
+ <div
+ *ngFor="let key of Object.keys(KEYWORD_CHECKBOX_LABELS) | slice:0:((Object.keys(KEYWORD_CHECKBOX_LABELS).length)/2); let k = index"
+ >
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ [(ngModel)]="newData[i]['keywordsData'][key]"
+ type="checkbox"
+ id="{{KEYWORD_CHECKBOX_LABELS[key]}}_{{i}}"
+ name="{{KEYWORD_CHECKBOX_LABELS[key]}}"
+ />
+ <label
+ class="form-check-label"
+ for="{{KEYWORD_CHECKBOX_LABELS[key]}}_{{i}}"
+ >
+ {{KEYWORD_CHECKBOX_LABELS[key]}}
+ </label>
+ <br />
+ </div>
+ </div>
+ </div>
+ <!--Row 2 (Prevent Overflow on mobile by splitting checkboxes into 2 rows)-->
+ <div class="d-flex flex-row justify-content-around">
+ <div
+ *ngFor="let key of Object.keys(KEYWORD_CHECKBOX_LABELS) | slice:3:(Object.keys(KEYWORD_CHECKBOX_LABELS).length); let k = index"
+ >
+ <div class="form-check">
+ <input
+ class="form-check-input"
+ [(ngModel)]="newData[i]['keywordsData'][key]"
+ type="checkbox"
+ id="{{KEYWORD_CHECKBOX_LABELS[key]}}"
+ name="{{KEYWORD_CHECKBOX_LABELS[key]}}"
+ />
+ <label
+ class="form-check-label"
+ for="{{KEYWORD_CHECKBOX_LABELS[key]}}"
+ >
+ {{KEYWORD_CHECKBOX_LABELS[key]}}
+ </label>
+ <br />
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="d-flex flex-row justify-content-center pt-2">
+ <div>
+ <button
+ id="add-team-button"
+ class="btn btn-secondary"
+ (click)="addTeam()"
+ >
+ Add team
+ </button>
+ </div>
+ <div>
+ <button
+ id="submit-button"
+ class="btn btn-success"
+ (click)="submitData()"
+ >
+ Submit
+ </button>
+ </div>
</div>
</div>
+
<div class="error">{{errorMessage}}</div>
</ng-container>
diff --git a/tools/go/go_mirrors.bzl b/tools/go/go_mirrors.bzl
index ee8b1b8..338cc95 100644
--- a/tools/go/go_mirrors.bzl
+++ b/tools/go/go_mirrors.bzl
@@ -1,11 +1,11 @@
# This file is auto-generated. Do not edit.
GO_MIRROR_INFO = {
"co_honnef_go_tools": {
- "filename": "co_honnef_go_tools__v0.0.0-20190523083050-ea95bdfd59fc.zip",
+ "filename": "co_honnef_go_tools__v0.0.1-2019.2.3.zip",
"importpath": "honnef.co/go/tools",
- "sha256": "eeaa82700e96ac5e803d7a9c32363332504beff8fbb1202492b4d43d5a5e7360",
- "strip_prefix": "honnef.co/go/tools@v0.0.0-20190523083050-ea95bdfd59fc",
- "version": "v0.0.0-20190523083050-ea95bdfd59fc",
+ "sha256": "539825114c487680f99df80f6107410e1e53bbfd5deb931b84d1faf2d221638e",
+ "strip_prefix": "honnef.co/go/tools@v0.0.1-2019.2.3",
+ "version": "v0.0.1-2019.2.3",
},
"com_github_antihax_optional": {
"filename": "com_github_antihax_optional__v1.0.0.zip",
@@ -77,6 +77,20 @@
"strip_prefix": "github.com/cockroachdb/apd@v1.1.0",
"version": "v1.1.0",
},
+ "com_github_coreos_go_systemd": {
+ "filename": "com_github_coreos_go_systemd__v0.0.0-20190719114852-fd7a80b32e1f.zip",
+ "importpath": "github.com/coreos/go-systemd",
+ "sha256": "22237f0aed3ab6018a1025c65f4f45b4c05f9aa0c0bb9ec880294273b9a15bf2",
+ "strip_prefix": "github.com/coreos/go-systemd@v0.0.0-20190719114852-fd7a80b32e1f",
+ "version": "v0.0.0-20190719114852-fd7a80b32e1f",
+ },
+ "com_github_creack_pty": {
+ "filename": "com_github_creack_pty__v1.1.7.zip",
+ "importpath": "github.com/creack/pty",
+ "sha256": "e7ea3403784d186aefbe84caed958f8cba2e72a04f30cdb291ece19bec39c8f3",
+ "strip_prefix": "github.com/creack/pty@v1.1.7",
+ "version": "v1.1.7",
+ },
"com_github_davecgh_go_spew": {
"filename": "com_github_davecgh_go_spew__v1.1.1.zip",
"importpath": "github.com/davecgh/go-spew",
@@ -105,6 +119,27 @@
"strip_prefix": "github.com/ghodss/yaml@v1.0.0",
"version": "v1.0.0",
},
+ "com_github_go_kit_log": {
+ "filename": "com_github_go_kit_log__v0.1.0.zip",
+ "importpath": "github.com/go-kit/log",
+ "sha256": "e0676df7357654a000008dfad3b6b211cba3595f32d3e220edd63a4c9d0d9254",
+ "strip_prefix": "github.com/go-kit/log@v0.1.0",
+ "version": "v0.1.0",
+ },
+ "com_github_go_logfmt_logfmt": {
+ "filename": "com_github_go_logfmt_logfmt__v0.5.0.zip",
+ "importpath": "github.com/go-logfmt/logfmt",
+ "sha256": "59a6b59ae3da84f7a58373844ca8d298f5007ce0e173437fc85c26d4fc76ca8b",
+ "strip_prefix": "github.com/go-logfmt/logfmt@v0.5.0",
+ "version": "v0.5.0",
+ },
+ "com_github_go_stack_stack": {
+ "filename": "com_github_go_stack_stack__v1.8.0.zip",
+ "importpath": "github.com/go-stack/stack",
+ "sha256": "78c2667c710f811307038634ffa43af442619acfeaf1efb593aa4e0ded9df48f",
+ "strip_prefix": "github.com/go-stack/stack@v1.8.0",
+ "version": "v1.8.0",
+ },
"com_github_gofrs_uuid": {
"filename": "com_github_gofrs_uuid__v4.0.0+incompatible.zip",
"importpath": "github.com/gofrs/uuid",
@@ -147,6 +182,13 @@
"strip_prefix": "github.com/google/go-querystring@v1.1.0",
"version": "v1.1.0",
},
+ "com_github_google_renameio": {
+ "filename": "com_github_google_renameio__v0.1.0.zip",
+ "importpath": "github.com/google/renameio",
+ "sha256": "b8510bb34078691a20b8e4902d371afe0eb171b2daf953f67cb3960d1926ccf3",
+ "strip_prefix": "github.com/google/renameio@v0.1.0",
+ "version": "v0.1.0",
+ },
"com_github_google_uuid": {
"filename": "com_github_google_uuid__v1.1.2.zip",
"importpath": "github.com/google/uuid",
@@ -161,19 +203,138 @@
"strip_prefix": "github.com/grpc-ecosystem/grpc-gateway@v1.16.0",
"version": "v1.16.0",
},
- "com_github_jackc_fake": {
- "filename": "com_github_jackc_fake__v0.0.0-20150926172116-812a484cc733.zip",
- "importpath": "github.com/jackc/fake",
- "sha256": "bf8b5b51ae03f572a70a0582dc663c5733bba9aca785d39bb0367797148e6d64",
- "strip_prefix": "github.com/jackc/fake@v0.0.0-20150926172116-812a484cc733",
- "version": "v0.0.0-20150926172116-812a484cc733",
+ "com_github_jackc_chunkreader": {
+ "filename": "com_github_jackc_chunkreader__v1.0.0.zip",
+ "importpath": "github.com/jackc/chunkreader",
+ "sha256": "e204c917e2652ffe047f5c8b031192757321f568654e3df8408bf04178df1408",
+ "strip_prefix": "github.com/jackc/chunkreader@v1.0.0",
+ "version": "v1.0.0",
},
- "com_github_jackc_pgx": {
- "filename": "com_github_jackc_pgx__v3.6.2+incompatible.zip",
- "importpath": "github.com/jackc/pgx",
- "sha256": "73675895baa0da97b2f0ce6e895c69b7c77ad994e30ce6a1add2abc3bb17e375",
- "strip_prefix": "github.com/jackc/pgx@v3.6.2+incompatible",
- "version": "v3.6.2+incompatible",
+ "com_github_jackc_chunkreader_v2": {
+ "filename": "com_github_jackc_chunkreader_v2__v2.0.1.zip",
+ "importpath": "github.com/jackc/chunkreader/v2",
+ "sha256": "6e3f4b7d9647f31061f6446ae10de71fc1407e64f84cd0949afac0cd231e8dd2",
+ "strip_prefix": "github.com/jackc/chunkreader/v2@v2.0.1",
+ "version": "v2.0.1",
+ },
+ "com_github_jackc_pgconn": {
+ "filename": "com_github_jackc_pgconn__v1.12.1.zip",
+ "importpath": "github.com/jackc/pgconn",
+ "sha256": "48d34064a1facff7766713d9224502e7376a5d90c1506f99a37c57bfceaf9636",
+ "strip_prefix": "github.com/jackc/pgconn@v1.12.1",
+ "version": "v1.12.1",
+ },
+ "com_github_jackc_pgio": {
+ "filename": "com_github_jackc_pgio__v1.0.0.zip",
+ "importpath": "github.com/jackc/pgio",
+ "sha256": "1a83c03d53f6a40339364cafcbbabb44238203c79ca0c9b98bf582d0df0e0468",
+ "strip_prefix": "github.com/jackc/pgio@v1.0.0",
+ "version": "v1.0.0",
+ },
+ "com_github_jackc_pgmock": {
+ "filename": "com_github_jackc_pgmock__v0.0.0-20210724152146-4ad1a8207f65.zip",
+ "importpath": "github.com/jackc/pgmock",
+ "sha256": "0fffd0a7a67dbdfafa04297e51028c6d2d08cd6691f3b6d78d7ae6502d3d4cf2",
+ "strip_prefix": "github.com/jackc/pgmock@v0.0.0-20210724152146-4ad1a8207f65",
+ "version": "v0.0.0-20210724152146-4ad1a8207f65",
+ },
+ "com_github_jackc_pgpassfile": {
+ "filename": "com_github_jackc_pgpassfile__v1.0.0.zip",
+ "importpath": "github.com/jackc/pgpassfile",
+ "sha256": "1cc79fb0b80f54b568afd3f4648dd1c349f746ad7c379df8d7f9e0eb1cac938b",
+ "strip_prefix": "github.com/jackc/pgpassfile@v1.0.0",
+ "version": "v1.0.0",
+ },
+ "com_github_jackc_pgproto3": {
+ "filename": "com_github_jackc_pgproto3__v1.1.0.zip",
+ "importpath": "github.com/jackc/pgproto3",
+ "sha256": "e3766bee50ed74e49a067b2c4797a2c69015cf104bf3f3624cd483a9e940b4ee",
+ "strip_prefix": "github.com/jackc/pgproto3@v1.1.0",
+ "version": "v1.1.0",
+ },
+ "com_github_jackc_pgproto3_v2": {
+ "filename": "com_github_jackc_pgproto3_v2__v2.3.0.zip",
+ "importpath": "github.com/jackc/pgproto3/v2",
+ "sha256": "6b702c372e13520636243d3be58922968f0630b67e23ba77326ef6ee4cada463",
+ "strip_prefix": "github.com/jackc/pgproto3/v2@v2.3.0",
+ "version": "v2.3.0",
+ },
+ "com_github_jackc_pgservicefile": {
+ "filename": "com_github_jackc_pgservicefile__v0.0.0-20200714003250-2b9c44734f2b.zip",
+ "importpath": "github.com/jackc/pgservicefile",
+ "sha256": "8422a25b9d2b0be05c66ee1ccfdbaab144ce98f1ac678bc647064c560d4cd6e2",
+ "strip_prefix": "github.com/jackc/pgservicefile@v0.0.0-20200714003250-2b9c44734f2b",
+ "version": "v0.0.0-20200714003250-2b9c44734f2b",
+ },
+ "com_github_jackc_pgtype": {
+ "filename": "com_github_jackc_pgtype__v1.11.0.zip",
+ "importpath": "github.com/jackc/pgtype",
+ "sha256": "6a257b81c0bd386d6241219a14ebd41d574a02aeaeb3942670c06441b864dcad",
+ "strip_prefix": "github.com/jackc/pgtype@v1.11.0",
+ "version": "v1.11.0",
+ },
+ "com_github_jackc_pgx_v4": {
+ "filename": "com_github_jackc_pgx_v4__v4.16.1.zip",
+ "importpath": "github.com/jackc/pgx/v4",
+ "sha256": "c3a169a68ff0e56f9f81eee4de4d2fd2a5ec7f4d6be159159325f4863c80bd10",
+ "strip_prefix": "github.com/jackc/pgx/v4@v4.16.1",
+ "version": "v4.16.1",
+ },
+ "com_github_jackc_puddle": {
+ "filename": "com_github_jackc_puddle__v1.2.1.zip",
+ "importpath": "github.com/jackc/puddle",
+ "sha256": "40d73550686666eb1f6df02b65008b2a4c98cfed1254dc4866e6ebe95fbc5c95",
+ "strip_prefix": "github.com/jackc/puddle@v1.2.1",
+ "version": "v1.2.1",
+ },
+ "com_github_jinzhu_inflection": {
+ "filename": "com_github_jinzhu_inflection__v1.0.0.zip",
+ "importpath": "github.com/jinzhu/inflection",
+ "sha256": "cf1087a6f6653ed5f366f85cf0110bbbf581d4e9bc8a4d1a9b56765d94b546c3",
+ "strip_prefix": "github.com/jinzhu/inflection@v1.0.0",
+ "version": "v1.0.0",
+ },
+ "com_github_jinzhu_now": {
+ "filename": "com_github_jinzhu_now__v1.1.4.zip",
+ "importpath": "github.com/jinzhu/now",
+ "sha256": "245473b8e50be3897751ec66dd6be93588de261920e0345b500f692924575872",
+ "strip_prefix": "github.com/jinzhu/now@v1.1.4",
+ "version": "v1.1.4",
+ },
+ "com_github_kisielk_gotool": {
+ "filename": "com_github_kisielk_gotool__v1.0.0.zip",
+ "importpath": "github.com/kisielk/gotool",
+ "sha256": "089dbba6e3aa09944fdb40d72acc86694e8bdde01cfc0f40fe0248309eb80a3f",
+ "strip_prefix": "github.com/kisielk/gotool@v1.0.0",
+ "version": "v1.0.0",
+ },
+ "com_github_konsorten_go_windows_terminal_sequences": {
+ "filename": "com_github_konsorten_go_windows_terminal_sequences__v1.0.2.zip",
+ "importpath": "github.com/konsorten/go-windows-terminal-sequences",
+ "sha256": "4d00d71b8de60bcaf454f8f867210ebcd05e75c0a7c2725904f71aa2f20fb08e",
+ "strip_prefix": "github.com/konsorten/go-windows-terminal-sequences@v1.0.2",
+ "version": "v1.0.2",
+ },
+ "com_github_kr_pretty": {
+ "filename": "com_github_kr_pretty__v0.1.0.zip",
+ "importpath": "github.com/kr/pretty",
+ "sha256": "06063d21457e06dc2aba4a5bd09771147ec3d8ab40b224f26e55c5a76089ca43",
+ "strip_prefix": "github.com/kr/pretty@v0.1.0",
+ "version": "v0.1.0",
+ },
+ "com_github_kr_pty": {
+ "filename": "com_github_kr_pty__v1.1.8.zip",
+ "importpath": "github.com/kr/pty",
+ "sha256": "d66e6fbc65e772289a7ff8c58ab2cdfb886253053b0cea11ba3ca1738b2d6bc6",
+ "strip_prefix": "github.com/kr/pty@v1.1.8",
+ "version": "v1.1.8",
+ },
+ "com_github_kr_text": {
+ "filename": "com_github_kr_text__v0.1.0.zip",
+ "importpath": "github.com/kr/text",
+ "sha256": "9363a4c8f1f3387a36014de51b477b831a13981fc59a5665f9d21609bea9e77c",
+ "strip_prefix": "github.com/kr/text@v0.1.0",
+ "version": "v0.1.0",
},
"com_github_lib_pq": {
"filename": "com_github_lib_pq__v1.10.2.zip",
@@ -182,6 +343,27 @@
"strip_prefix": "github.com/lib/pq@v1.10.2",
"version": "v1.10.2",
},
+ "com_github_masterminds_semver_v3": {
+ "filename": "com_github_masterminds_semver_v3__v3.1.1.zip",
+ "importpath": "github.com/Masterminds/semver/v3",
+ "sha256": "0a46c7403dfeda09b0821e851f8e1cec8f1ea4276281e42ea399da5bc5bf0704",
+ "strip_prefix": "github.com/Masterminds/semver/v3@v3.1.1",
+ "version": "v3.1.1",
+ },
+ "com_github_mattn_go_colorable": {
+ "filename": "com_github_mattn_go_colorable__v0.1.6.zip",
+ "importpath": "github.com/mattn/go-colorable",
+ "sha256": "0da5d3779775f6fe5d007e7ec8e0afc136c4bd7b8c9b5cd73254db26773cf4dc",
+ "strip_prefix": "github.com/mattn/go-colorable@v0.1.6",
+ "version": "v0.1.6",
+ },
+ "com_github_mattn_go_isatty": {
+ "filename": "com_github_mattn_go_isatty__v0.0.12.zip",
+ "importpath": "github.com/mattn/go-isatty",
+ "sha256": "07941d24e0894c29dc42bcd29d644815cd7b5ee84e3c14bbe6d51ad13efcbf07",
+ "strip_prefix": "github.com/mattn/go-isatty@v0.0.12",
+ "version": "v0.0.12",
+ },
"com_github_phst_runfiles": {
"filename": "com_github_phst_runfiles__v0.0.0-20220125203201-388095b3a22d.zip",
"importpath": "github.com/phst/runfiles",
@@ -217,6 +399,34 @@
"strip_prefix": "github.com/rogpeppe/fastuuid@v1.2.0",
"version": "v1.2.0",
},
+ "com_github_rogpeppe_go_internal": {
+ "filename": "com_github_rogpeppe_go_internal__v1.3.0.zip",
+ "importpath": "github.com/rogpeppe/go-internal",
+ "sha256": "191b95c35d85a5683cee6e303a08b4d103bf9de9ececdc6904f21ed90c094b0a",
+ "strip_prefix": "github.com/rogpeppe/go-internal@v1.3.0",
+ "version": "v1.3.0",
+ },
+ "com_github_rs_xid": {
+ "filename": "com_github_rs_xid__v1.2.1.zip",
+ "importpath": "github.com/rs/xid",
+ "sha256": "4abdedc4de69adcb9a4575f99c59d8ab542191e1800b6a91e12a4e9ea8da0026",
+ "strip_prefix": "github.com/rs/xid@v1.2.1",
+ "version": "v1.2.1",
+ },
+ "com_github_rs_zerolog": {
+ "filename": "com_github_rs_zerolog__v1.15.0.zip",
+ "importpath": "github.com/rs/zerolog",
+ "sha256": "8e98c48e7fd132aafbf129664e8fd65229d067d772bff4bd712a497b7a2f00c4",
+ "strip_prefix": "github.com/rs/zerolog@v1.15.0",
+ "version": "v1.15.0",
+ },
+ "com_github_satori_go_uuid": {
+ "filename": "com_github_satori_go_uuid__v1.2.0.zip",
+ "importpath": "github.com/satori/go.uuid",
+ "sha256": "4f741306a0cbe97581e34a638531bcafe3c2848150539a2ec2ba12c5e3e6cbdd",
+ "strip_prefix": "github.com/satori/go.uuid@v1.2.0",
+ "version": "v1.2.0",
+ },
"com_github_shopspring_decimal": {
"filename": "com_github_shopspring_decimal__v1.2.0.zip",
"importpath": "github.com/shopspring/decimal",
@@ -224,12 +434,19 @@
"strip_prefix": "github.com/shopspring/decimal@v1.2.0",
"version": "v1.2.0",
},
+ "com_github_sirupsen_logrus": {
+ "filename": "com_github_sirupsen_logrus__v1.4.2.zip",
+ "importpath": "github.com/sirupsen/logrus",
+ "sha256": "9a8e55830261a4b1c9350d7c45db029c8586c0b2d934d1224cde469425031edd",
+ "strip_prefix": "github.com/sirupsen/logrus@v1.4.2",
+ "version": "v1.4.2",
+ },
"com_github_stretchr_objx": {
- "filename": "com_github_stretchr_objx__v0.1.0.zip",
+ "filename": "com_github_stretchr_objx__v0.2.0.zip",
"importpath": "github.com/stretchr/objx",
- "sha256": "1fa10dab404ed7fc8ed2a033f8784187d5df3513ced3841ce39e46d37850eb1d",
- "strip_prefix": "github.com/stretchr/objx@v0.1.0",
- "version": "v0.1.0",
+ "sha256": "5517d43cfb7e628b9c2c64010b934e346cd24726e3d6eaf02b7f86e10752e968",
+ "strip_prefix": "github.com/stretchr/objx@v0.2.0",
+ "version": "v0.2.0",
},
"com_github_stretchr_testify": {
"filename": "com_github_stretchr_testify__v1.7.0.zip",
@@ -238,6 +455,13 @@
"strip_prefix": "github.com/stretchr/testify@v1.7.0",
"version": "v1.7.0",
},
+ "com_github_zenazn_goji": {
+ "filename": "com_github_zenazn_goji__v0.9.0.zip",
+ "importpath": "github.com/zenazn/goji",
+ "sha256": "0807a255d9d715d18427a6eedd8e4f5a22670b09e5f45fddd229c1ae38da25a9",
+ "strip_prefix": "github.com/zenazn/goji@v0.9.0",
+ "version": "v0.9.0",
+ },
"com_google_cloud_go": {
"filename": "com_google_cloud_go__v0.34.0.zip",
"importpath": "cloud.google.com/go",
@@ -246,11 +470,25 @@
"version": "v0.34.0",
},
"in_gopkg_check_v1": {
- "filename": "in_gopkg_check_v1__v0.0.0-20161208181325-20d25e280405.zip",
+ "filename": "in_gopkg_check_v1__v1.0.0-20180628173108-788fd7840127.zip",
"importpath": "gopkg.in/check.v1",
- "sha256": "4e1817f964ca34e545b81afda0325a5e89cf58de2e413d8207c0afddd0fdc15c",
- "strip_prefix": "gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405",
- "version": "v0.0.0-20161208181325-20d25e280405",
+ "sha256": "4bc535ed2aac48a231af8b6005a0b5f6069dadab9a3d65b1e9f1fe91c74d8e61",
+ "strip_prefix": "gopkg.in/check.v1@v1.0.0-20180628173108-788fd7840127",
+ "version": "v1.0.0-20180628173108-788fd7840127",
+ },
+ "in_gopkg_errgo_v2": {
+ "filename": "in_gopkg_errgo_v2__v2.1.0.zip",
+ "importpath": "gopkg.in/errgo.v2",
+ "sha256": "6b8954819a20ec52982a206fd3eb94629ff53c5790aa77534e6d8daf7de01bee",
+ "strip_prefix": "gopkg.in/errgo.v2@v2.1.0",
+ "version": "v2.1.0",
+ },
+ "in_gopkg_inconshreveable_log15_v2": {
+ "filename": "in_gopkg_inconshreveable_log15_v2__v2.0.0-20180818164646-67afb5ed74ec.zip",
+ "importpath": "gopkg.in/inconshreveable/log15.v2",
+ "sha256": "799307ed46ca30ca0ac2dc0332f3673814b8ff6cc1ee905a462ccfd438e8e695",
+ "strip_prefix": "gopkg.in/inconshreveable/log15.v2@v2.0.0-20180818164646-67afb5ed74ec",
+ "version": "v2.0.0-20180818164646-67afb5ed74ec",
},
"in_gopkg_yaml_v2": {
"filename": "in_gopkg_yaml_v2__v2.2.3.zip",
@@ -266,6 +504,20 @@
"strip_prefix": "gopkg.in/yaml.v3@v3.0.0-20200313102051-9f266ea9e77c",
"version": "v3.0.0-20200313102051-9f266ea9e77c",
},
+ "io_gorm_driver_postgres": {
+ "filename": "io_gorm_driver_postgres__v1.3.7.zip",
+ "importpath": "gorm.io/driver/postgres",
+ "sha256": "b38fed3060ea8ee200d50666a9c6230f2c387d4ab930b70dd859b93f5fac7771",
+ "strip_prefix": "gorm.io/driver/postgres@v1.3.7",
+ "version": "v1.3.7",
+ },
+ "io_gorm_gorm": {
+ "filename": "io_gorm_gorm__v1.23.5.zip",
+ "importpath": "gorm.io/gorm",
+ "sha256": "34219a6d2ac9b9c340f811e5863a98b150db6d1fd5b8f02777299863c1628e0f",
+ "strip_prefix": "gorm.io/gorm@v1.23.5",
+ "version": "v1.23.5",
+ },
"io_opentelemetry_go_proto_otlp": {
"filename": "io_opentelemetry_go_proto_otlp__v0.7.0.zip",
"importpath": "go.opentelemetry.io/proto/otlp",
@@ -302,11 +554,11 @@
"version": "v1.26.0",
},
"org_golang_x_crypto": {
- "filename": "org_golang_x_crypto__v0.0.0-20210711020723-a769d52b0f97.zip",
+ "filename": "org_golang_x_crypto__v0.0.0-20210921155107-089bfa567519.zip",
"importpath": "golang.org/x/crypto",
- "sha256": "b2b28fcf49bf385183f0369851145ddd93989f68d9e675db536a3dd482ca6d76",
- "strip_prefix": "golang.org/x/crypto@v0.0.0-20210711020723-a769d52b0f97",
- "version": "v0.0.0-20210711020723-a769d52b0f97",
+ "sha256": "eb2426a7891915213cc5da1da7b6fc6e9e2cf253d518d8e169e038e287f414e3",
+ "strip_prefix": "golang.org/x/crypto@v0.0.0-20210921155107-089bfa567519",
+ "version": "v0.0.0-20210921155107-089bfa567519",
},
"org_golang_x_exp": {
"filename": "org_golang_x_exp__v0.0.0-20190121172915-509febef88a4.zip",
@@ -316,11 +568,18 @@
"version": "v0.0.0-20190121172915-509febef88a4",
},
"org_golang_x_lint": {
- "filename": "org_golang_x_lint__v0.0.0-20190313153728-d0100b6bd8b3.zip",
+ "filename": "org_golang_x_lint__v0.0.0-20190930215403-16217165b5de.zip",
"importpath": "golang.org/x/lint",
- "sha256": "5c7bb9792bdc4ec4cf1af525cf9998f8a958daf6495852c9a7dbb71738f2f10a",
- "strip_prefix": "golang.org/x/lint@v0.0.0-20190313153728-d0100b6bd8b3",
- "version": "v0.0.0-20190313153728-d0100b6bd8b3",
+ "sha256": "91323fe1a77f13de722a0ce8efc5c5f2da4f26216d858acec64cb23c956fa163",
+ "strip_prefix": "golang.org/x/lint@v0.0.0-20190930215403-16217165b5de",
+ "version": "v0.0.0-20190930215403-16217165b5de",
+ },
+ "org_golang_x_mod": {
+ "filename": "org_golang_x_mod__v0.1.1-0.20191105210325-c90efee705ee.zip",
+ "importpath": "golang.org/x/mod",
+ "sha256": "b1e6cb975c69d29974b4f77fd8a0f2f7e916a1fa971bab60fdd45ffe80a29f32",
+ "strip_prefix": "golang.org/x/mod@v0.1.1-0.20191105210325-c90efee705ee",
+ "version": "v0.1.1-0.20191105210325-c90efee705ee",
},
"org_golang_x_net": {
"filename": "org_golang_x_net__v0.0.0-20210226172049-e18ecbb05110.zip",
@@ -358,18 +617,18 @@
"version": "v0.0.0-20201126162022-7de9c90e9dd1",
},
"org_golang_x_text": {
- "filename": "org_golang_x_text__v0.3.6.zip",
+ "filename": "org_golang_x_text__v0.3.7.zip",
"importpath": "golang.org/x/text",
- "sha256": "2afade648a4cb240afb7b3bf8e3719b615169c90d6281bd6d4ba34629c744579",
- "strip_prefix": "golang.org/x/text@v0.3.6",
- "version": "v0.3.6",
+ "sha256": "e1a9115e61a38da8bdc893d0ba83b65f89cc1114f152a98eb572c5ea6551e8d4",
+ "strip_prefix": "golang.org/x/text@v0.3.7",
+ "version": "v0.3.7",
},
"org_golang_x_tools": {
- "filename": "org_golang_x_tools__v0.0.0-20190524140312-2c0ae7006135.zip",
+ "filename": "org_golang_x_tools__v0.0.0-20200103221440-774c71fcf114.zip",
"importpath": "golang.org/x/tools",
- "sha256": "86687e8cd5adccf8809ba031e59146d0c89047b6267aacc785ffc20b0ce6b735",
- "strip_prefix": "golang.org/x/tools@v0.0.0-20190524140312-2c0ae7006135",
- "version": "v0.0.0-20190524140312-2c0ae7006135",
+ "sha256": "1c26b6b98d945255dfb6112d71135de3919350250e44e552a7089f724d0b7bfc",
+ "strip_prefix": "golang.org/x/tools@v0.0.0-20200103221440-774c71fcf114",
+ "version": "v0.0.0-20200103221440-774c71fcf114",
},
"org_golang_x_xerrors": {
"filename": "org_golang_x_xerrors__v0.0.0-20200804184101-5ec99f83aff1.zip",
@@ -378,4 +637,32 @@
"strip_prefix": "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
"version": "v0.0.0-20200804184101-5ec99f83aff1",
},
+ "org_uber_go_atomic": {
+ "filename": "org_uber_go_atomic__v1.6.0.zip",
+ "importpath": "go.uber.org/atomic",
+ "sha256": "c5e1e9f48017d7c7f7bb4532235e33242a1508bee68abe3cd301b68fe8ecd552",
+ "strip_prefix": "go.uber.org/atomic@v1.6.0",
+ "version": "v1.6.0",
+ },
+ "org_uber_go_multierr": {
+ "filename": "org_uber_go_multierr__v1.5.0.zip",
+ "importpath": "go.uber.org/multierr",
+ "sha256": "64053b7f6129cf2588f9b9ef1e934a26a0381da0002add973ec99f1294c1fc1e",
+ "strip_prefix": "go.uber.org/multierr@v1.5.0",
+ "version": "v1.5.0",
+ },
+ "org_uber_go_tools": {
+ "filename": "org_uber_go_tools__v0.0.0-20190618225709-2cfd321de3ee.zip",
+ "importpath": "go.uber.org/tools",
+ "sha256": "988dba9c5074080240d33d98e8ce511532f728698db7a9a4ac316c02c94030d6",
+ "strip_prefix": "go.uber.org/tools@v0.0.0-20190618225709-2cfd321de3ee",
+ "version": "v0.0.0-20190618225709-2cfd321de3ee",
+ },
+ "org_uber_go_zap": {
+ "filename": "org_uber_go_zap__v1.13.0.zip",
+ "importpath": "go.uber.org/zap",
+ "sha256": "4b4d15be7b4ce8029ab7c90f2fcb4c98e655172ebaa5cdbe234401081000fa26",
+ "strip_prefix": "go.uber.org/zap@v1.13.0",
+ "version": "v1.13.0",
+ },
}
diff --git a/tools/python/generate_pip_packages_in_docker.sh b/tools/python/generate_pip_packages_in_docker.sh
index a3af2ae..13d51b7 100755
--- a/tools/python/generate_pip_packages_in_docker.sh
+++ b/tools/python/generate_pip_packages_in_docker.sh
@@ -101,6 +101,17 @@
"${PIP_BIN[@]}" install auditwheel
for wheel in "${wheels_built_from_source[@]}"; do
wheel_path="${SCRIPT_DIR}/wheelhouse_tmp/${wheel}"
+
+ # Skip the pygobject wheel for now. I have no idea why, but repairing it will
+ # prevent it from finding certain files. Possibly some issue with paths
+ # relative to the .so file.
+ # TODO(phil): Figure out what's wrong with the repaired wheel.
+ if [[ "${wheel}" == PyGObject-*.whl ]]; then
+ echo "Not repairing ${wheel} because of issues."
+ cp "${wheel_path}" "${SCRIPT_DIR}"/wheelhouse/
+ continue
+ fi
+
echo "Repairing wheel ${wheel}"
if ! auditwheel show "${wheel_path}"; then
echo "Assuming ${wheel} is a non-platform wheel. Skipping."
diff --git a/tools/python/requirements.lock.txt b/tools/python/requirements.lock.txt
index a48b2a7..93ac065 100644
--- a/tools/python/requirements.lock.txt
+++ b/tools/python/requirements.lock.txt
@@ -4,9 +4,9 @@
#
# bazel run //tools/python:requirements.update
#
-certifi==2022.9.14 \
- --hash=sha256:36973885b9542e6bd01dea287b2b4b3b21236307c56324fcc3f1160f2d655ed5 \
- --hash=sha256:e232343de1ab72c2aa521b625c80f699e356830fd0e2c620b465b304b17b0516
+certifi==2022.9.24 \
+ --hash=sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14 \
+ --hash=sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382
# via requests
charset-normalizer==2.1.1 \
--hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \
@@ -16,13 +16,84 @@
--hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
--hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
# via mkdocs
+contourpy==1.0.6 \
+ --hash=sha256:0236875c5a0784215b49d00ebbe80c5b6b5d5244b3655a36dda88105334dea17 \
+ --hash=sha256:03d1b9c6b44a9e30d554654c72be89af94fab7510b4b9f62356c64c81cec8b7d \
+ --hash=sha256:0537cc1195245bbe24f2913d1f9211b8f04eb203de9044630abd3664c6cc339c \
+ --hash=sha256:06ca79e1efbbe2df795822df2fa173d1a2b38b6e0f047a0ec7903fbca1d1847e \
+ --hash=sha256:08e8d09d96219ace6cb596506fb9b64ea5f270b2fb9121158b976d88871fcfd1 \
+ --hash=sha256:0b1e66346acfb17694d46175a0cea7d9036f12ed0c31dfe86f0f405eedde2bdd \
+ --hash=sha256:0b97454ed5b1368b66ed414c754cba15b9750ce69938fc6153679787402e4cdf \
+ --hash=sha256:0e4854cc02006ad6684ce092bdadab6f0912d131f91c2450ce6dbdea78ee3c0b \
+ --hash=sha256:12a7dc8439544ed05c6553bf026d5e8fa7fad48d63958a95d61698df0e00092b \
+ --hash=sha256:1b1ee48a130da4dd0eb8055bbab34abf3f6262957832fd575e0cab4979a15a41 \
+ --hash=sha256:1c0e1308307a75e07d1f1b5f0f56b5af84538a5e9027109a7bcf6cb47c434e72 \
+ --hash=sha256:1dedf4c64185a216c35eb488e6f433297c660321275734401760dafaeb0ad5c2 \
+ --hash=sha256:208bc904889c910d95aafcf7be9e677726df9ef71e216780170dbb7e37d118fa \
+ --hash=sha256:211dfe2bd43bf5791d23afbe23a7952e8ac8b67591d24be3638cabb648b3a6eb \
+ --hash=sha256:341330ed19074f956cb20877ad8d2ae50e458884bfa6a6df3ae28487cc76c768 \
+ --hash=sha256:344cb3badf6fc7316ad51835f56ac387bdf86c8e1b670904f18f437d70da4183 \
+ --hash=sha256:358f6364e4873f4d73360b35da30066f40387dd3c427a3e5432c6b28dd24a8fa \
+ --hash=sha256:371f6570a81dfdddbb837ba432293a63b4babb942a9eb7aaa699997adfb53278 \
+ --hash=sha256:375d81366afd547b8558c4720337218345148bc2fcffa3a9870cab82b29667f2 \
+ --hash=sha256:3a1917d3941dd58732c449c810fa7ce46cc305ce9325a11261d740118b85e6f3 \
+ --hash=sha256:4081918147fc4c29fad328d5066cfc751da100a1098398742f9f364be63803fc \
+ --hash=sha256:444fb776f58f4906d8d354eb6f6ce59d0a60f7b6a720da6c1ccb839db7c80eb9 \
+ --hash=sha256:46deb310a276cc5c1fd27958e358cce68b1e8a515fa5a574c670a504c3a3fe30 \
+ --hash=sha256:494efed2c761f0f37262815f9e3c4bb9917c5c69806abdee1d1cb6611a7174a0 \
+ --hash=sha256:50627bf76abb6ba291ad08db583161939c2c5fab38c38181b7833423ab9c7de3 \
+ --hash=sha256:5641927cc5ae66155d0c80195dc35726eae060e7defc18b7ab27600f39dd1fe7 \
+ --hash=sha256:5b117d29433fc8393b18a696d794961464e37afb34a6eeb8b2c37b5f4128a83e \
+ --hash=sha256:613c665529899b5d9fade7e5d1760111a0b011231277a0d36c49f0d3d6914bd6 \
+ --hash=sha256:6e459ebb8bb5ee4c22c19cc000174f8059981971a33ce11e17dddf6aca97a142 \
+ --hash=sha256:6f56515e7c6fae4529b731f6c117752247bef9cdad2b12fc5ddf8ca6a50965a5 \
+ --hash=sha256:730c27978a0003b47b359935478b7d63fd8386dbb2dcd36c1e8de88cbfc1e9de \
+ --hash=sha256:75a2e638042118118ab39d337da4c7908c1af74a8464cad59f19fbc5bbafec9b \
+ --hash=sha256:78ced51807ccb2f45d4ea73aca339756d75d021069604c2fccd05390dc3c28eb \
+ --hash=sha256:7ee394502026d68652c2824348a40bf50f31351a668977b51437131a90d777ea \
+ --hash=sha256:8468b40528fa1e15181cccec4198623b55dcd58306f8815a793803f51f6c474a \
+ --hash=sha256:84c593aeff7a0171f639da92cb86d24954bbb61f8a1b530f74eb750a14685832 \
+ --hash=sha256:913bac9d064cff033cf3719e855d4f1db9f1c179e0ecf3ba9fdef21c21c6a16a \
+ --hash=sha256:9447c45df407d3ecb717d837af3b70cfef432138530712263730783b3d016512 \
+ --hash=sha256:9b0e7fe7f949fb719b206548e5cde2518ffb29936afa4303d8a1c4db43dcb675 \
+ --hash=sha256:9bc407a6af672da20da74823443707e38ece8b93a04009dca25856c2d9adadb1 \
+ --hash=sha256:9e8e686a6db92a46111a1ee0ee6f7fbfae4048f0019de207149f43ac1812cf95 \
+ --hash=sha256:9fc4e7973ed0e1fe689435842a6e6b330eb7ccc696080dda9a97b1a1b78e41db \
+ --hash=sha256:a457ee72d9032e86730f62c5eeddf402e732fdf5ca8b13b41772aa8ae13a4563 \
+ --hash=sha256:a628bba09ba72e472bf7b31018b6281fd4cc903f0888049a3724afba13b6e0b8 \
+ --hash=sha256:a79d239fc22c3b8d9d3de492aa0c245533f4f4c7608e5749af866949c0f1b1b9 \
+ --hash=sha256:aa4674cf3fa2bd9c322982644967f01eed0c91bb890f624e0e0daf7a5c3383e9 \
+ --hash=sha256:acd2bd02f1a7adff3a1f33e431eb96ab6d7987b039d2946a9b39fe6fb16a1036 \
+ --hash=sha256:b3b1bd7577c530eaf9d2bc52d1a93fef50ac516a8b1062c3d1b9bcec9ebe329b \
+ --hash=sha256:b48d94386f1994db7c70c76b5808c12e23ed7a4ee13693c2fc5ab109d60243c0 \
+ --hash=sha256:b64f747e92af7da3b85631a55d68c45a2d728b4036b03cdaba4bd94bcc85bd6f \
+ --hash=sha256:b98c820608e2dca6442e786817f646d11057c09a23b68d2b3737e6dcb6e4a49b \
+ --hash=sha256:c1baa49ab9fedbf19d40d93163b7d3e735d9cd8d5efe4cce9907902a6dad391f \
+ --hash=sha256:c38c6536c2d71ca2f7e418acaf5bca30a3af7f2a2fa106083c7d738337848dbe \
+ --hash=sha256:c78bfbc1a7bff053baf7e508449d2765964d67735c909b583204e3240a2aca45 \
+ --hash=sha256:cd2bc0c8f2e8de7dd89a7f1c10b8844e291bca17d359373203ef2e6100819edd \
+ --hash=sha256:d2eff2af97ea0b61381828b1ad6cd249bbd41d280e53aea5cccd7b2b31b8225c \
+ --hash=sha256:d8834c14b8c3dd849005e06703469db9bf96ba2d66a3f88ecc539c9a8982e0ee \
+ --hash=sha256:d912f0154a20a80ea449daada904a7eb6941c83281a9fab95de50529bfc3a1da \
+ --hash=sha256:da1ef35fd79be2926ba80fbb36327463e3656c02526e9b5b4c2b366588b74d9a \
+ --hash=sha256:dbe6fe7a1166b1ddd7b6d887ea6fa8389d3f28b5ed3f73a8f40ece1fc5a3d340 \
+ --hash=sha256:dcd556c8fc37a342dd636d7eef150b1399f823a4462f8c968e11e1ebeabee769 \
+ --hash=sha256:e13b31d1b4b68db60b3b29f8e337908f328c7f05b9add4b1b5c74e0691180109 \
+ --hash=sha256:e1739496c2f0108013629aa095cc32a8c6363444361960c07493818d0dea2da4 \
+ --hash=sha256:e43255a83835a129ef98f75d13d643844d8c646b258bebd11e4a0975203e018f \
+ --hash=sha256:e626cefff8491bce356221c22af5a3ea528b0b41fbabc719c00ae233819ea0bf \
+ --hash=sha256:eadad75bf91897f922e0fb3dca1b322a58b1726a953f98c2e5f0606bd8408621 \
+ --hash=sha256:f33da6b5d19ad1bb5e7ad38bb8ba5c426d2178928bc2b2c44e8823ea0ecb6ff3 \
+ --hash=sha256:f4052a8a4926d4468416fc7d4b2a7b2a3e35f25b39f4061a7e2a3a2748c4fc48 \
+ --hash=sha256:f6ca38dd8d988eca8f07305125dec6f54ac1c518f1aaddcc14d08c01aebb6efc
+ # via matplotlib
cycler==0.11.0 \
--hash=sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3 \
--hash=sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f
# via matplotlib
-fonttools==4.28.5 \
- --hash=sha256:545c05d0f7903a863c2020e07b8f0a57517f2c40d940bded77076397872d14ca \
- --hash=sha256:edf251d5d2cc0580d5f72de4621c338d8c66c5f61abb50cf486640f73c8194d5
+fonttools==4.38.0 \
+ --hash=sha256:2bb244009f9bf3fa100fc3ead6aeb99febe5985fa20afbfbaa2f8946c2fbdaf1 \
+ --hash=sha256:820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb
# via matplotlib
ghp-import==2.1.0 \
--hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \
@@ -32,9 +103,9 @@
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
--hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
# via requests
-importlib-metadata==5.0.0 \
- --hash=sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab \
- --hash=sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43
+importlib-metadata==5.1.0 \
+ --hash=sha256:d5059f9f1e8e41f80e9c56c2ee58811450c31984dfa625329ffd7c0dad88a73b \
+ --hash=sha256:d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313
# via
# markdown
# mkdocs
@@ -44,51 +115,75 @@
# via
# -r tools/python/requirements.txt
# mkdocs
-kiwisolver==1.3.2 \
- --hash=sha256:0007840186bacfaa0aba4466d5890334ea5938e0bb7e28078a0eb0e63b5b59d5 \
- --hash=sha256:19554bd8d54cf41139f376753af1a644b63c9ca93f8f72009d50a2080f870f77 \
- --hash=sha256:1d45d1c74f88b9f41062716c727f78f2a59a5476ecbe74956fafb423c5c87a76 \
- --hash=sha256:1d819553730d3c2724582124aee8a03c846ec4362ded1034c16fb3ef309264e6 \
- --hash=sha256:2210f28778c7d2ee13f3c2a20a3a22db889e75f4ec13a21072eabb5693801e84 \
- --hash=sha256:22521219ca739654a296eea6d4367703558fba16f98688bd8ce65abff36eaa84 \
- --hash=sha256:25405f88a37c5f5bcba01c6e350086d65e7465fd1caaf986333d2a045045a223 \
- --hash=sha256:2b65bd35f3e06a47b5c30ea99e0c2b88f72c6476eedaf8cfbc8e66adb5479dcf \
- --hash=sha256:2ddb500a2808c100e72c075cbb00bf32e62763c82b6a882d403f01a119e3f402 \
- --hash=sha256:2f8f6c8f4f1cff93ca5058d6ec5f0efda922ecb3f4c5fb76181f327decff98b8 \
- --hash=sha256:30fa008c172355c7768159983a7270cb23838c4d7db73d6c0f6b60dde0d432c6 \
- --hash=sha256:3dbb3cea20b4af4f49f84cffaf45dd5f88e8594d18568e0225e6ad9dec0e7967 \
- --hash=sha256:4116ba9a58109ed5e4cb315bdcbff9838f3159d099ba5259c7c7fb77f8537492 \
- --hash=sha256:44e6adf67577dbdfa2d9f06db9fbc5639afefdb5bf2b4dfec25c3a7fbc619536 \
- --hash=sha256:5326ddfacbe51abf9469fe668944bc2e399181a2158cb5d45e1d40856b2a0589 \
- --hash=sha256:70adc3658138bc77a36ce769f5f183169bc0a2906a4f61f09673f7181255ac9b \
- --hash=sha256:72be6ebb4e92520b9726d7146bc9c9b277513a57a38efcf66db0620aec0097e0 \
- --hash=sha256:7843b1624d6ccca403a610d1277f7c28ad184c5aa88a1750c1a999754e65b439 \
- --hash=sha256:7ba5a1041480c6e0a8b11a9544d53562abc2d19220bfa14133e0cdd9967e97af \
- --hash=sha256:80efd202108c3a4150e042b269f7c78643420cc232a0a771743bb96b742f838f \
- --hash=sha256:82f49c5a79d3839bc8f38cb5f4bfc87e15f04cbafa5fbd12fb32c941cb529cfb \
- --hash=sha256:83d2c9db5dfc537d0171e32de160461230eb14663299b7e6d18ca6dca21e4977 \
- --hash=sha256:8d93a1095f83e908fc253f2fb569c2711414c0bfd451cab580466465b235b470 \
- --hash=sha256:8dc3d842fa41a33fe83d9f5c66c0cc1f28756530cd89944b63b072281e852031 \
- --hash=sha256:9661a04ca3c950a8ac8c47f53cbc0b530bce1b52f516a1e87b7736fec24bfff0 \
- --hash=sha256:a498bcd005e8a3fedd0022bb30ee0ad92728154a8798b703f394484452550507 \
- --hash=sha256:a7a4cf5bbdc861987a7745aed7a536c6405256853c94abc9f3287c3fa401b174 \
- --hash=sha256:b5074fb09429f2b7bc82b6fb4be8645dcbac14e592128beeff5461dcde0af09f \
- --hash=sha256:b6a5431940f28b6de123de42f0eb47b84a073ee3c3345dc109ad550a3307dd28 \
- --hash=sha256:ba677bcaff9429fd1bf01648ad0901cea56c0d068df383d5f5856d88221fe75b \
- --hash=sha256:bcadb05c3d4794eb9eee1dddf1c24215c92fb7b55a80beae7a60530a91060560 \
- --hash=sha256:bf7eb45d14fc036514c09554bf983f2a72323254912ed0c3c8e697b62c4c158f \
- --hash=sha256:c358721aebd40c243894298f685a19eb0491a5c3e0b923b9f887ef1193ddf829 \
- --hash=sha256:c4550a359c5157aaf8507e6820d98682872b9100ce7607f8aa070b4b8af6c298 \
- --hash=sha256:c6572c2dab23c86a14e82c245473d45b4c515314f1f859e92608dcafbd2f19b8 \
- --hash=sha256:cba430db673c29376135e695c6e2501c44c256a81495da849e85d1793ee975ad \
- --hash=sha256:dedc71c8eb9c5096037766390172c34fb86ef048b8e8958b4e484b9e505d66bc \
- --hash=sha256:e6f5eb2f53fac7d408a45fbcdeda7224b1cfff64919d0f95473420a931347ae9 \
- --hash=sha256:ec2eba188c1906b05b9b49ae55aae4efd8150c61ba450e6721f64620c50b59eb \
- --hash=sha256:ee040a7de8d295dbd261ef2d6d3192f13e2b08ec4a954de34a6fb8ff6422e24c \
- --hash=sha256:eedd3b59190885d1ebdf6c5e0ca56828beb1949b4dfe6e5d0256a461429ac386 \
- --hash=sha256:f441422bb313ab25de7b3dbfd388e790eceb76ce01a18199ec4944b369017009 \
- --hash=sha256:f8eb7b6716f5b50e9c06207a14172cf2de201e41912ebe732846c02c830455b9 \
- --hash=sha256:fc4453705b81d03568d5b808ad8f09c77c47534f6ac2e72e733f9ca4714aa75c
+kiwisolver==1.4.4 \
+ --hash=sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b \
+ --hash=sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166 \
+ --hash=sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c \
+ --hash=sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c \
+ --hash=sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0 \
+ --hash=sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4 \
+ --hash=sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9 \
+ --hash=sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286 \
+ --hash=sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767 \
+ --hash=sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c \
+ --hash=sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6 \
+ --hash=sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b \
+ --hash=sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004 \
+ --hash=sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf \
+ --hash=sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494 \
+ --hash=sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac \
+ --hash=sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626 \
+ --hash=sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766 \
+ --hash=sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514 \
+ --hash=sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6 \
+ --hash=sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f \
+ --hash=sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d \
+ --hash=sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191 \
+ --hash=sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d \
+ --hash=sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51 \
+ --hash=sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f \
+ --hash=sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8 \
+ --hash=sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454 \
+ --hash=sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb \
+ --hash=sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da \
+ --hash=sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8 \
+ --hash=sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de \
+ --hash=sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a \
+ --hash=sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9 \
+ --hash=sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008 \
+ --hash=sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3 \
+ --hash=sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32 \
+ --hash=sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938 \
+ --hash=sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1 \
+ --hash=sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9 \
+ --hash=sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d \
+ --hash=sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824 \
+ --hash=sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b \
+ --hash=sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd \
+ --hash=sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2 \
+ --hash=sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5 \
+ --hash=sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69 \
+ --hash=sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3 \
+ --hash=sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae \
+ --hash=sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597 \
+ --hash=sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e \
+ --hash=sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955 \
+ --hash=sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca \
+ --hash=sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a \
+ --hash=sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea \
+ --hash=sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede \
+ --hash=sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4 \
+ --hash=sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6 \
+ --hash=sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686 \
+ --hash=sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408 \
+ --hash=sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871 \
+ --hash=sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29 \
+ --hash=sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750 \
+ --hash=sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897 \
+ --hash=sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0 \
+ --hash=sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2 \
+ --hash=sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09 \
+ --hash=sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c
# via matplotlib
markdown==3.3.7 \
--hash=sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874 \
@@ -136,84 +231,89 @@
--hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \
--hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7
# via jinja2
-matplotlib==3.5.1 \
- --hash=sha256:14334b9902ec776461c4b8c6516e26b450f7ebe0b3ef8703bf5cdfbbaecf774a \
- --hash=sha256:2252bfac85cec7af4a67e494bfccf9080bcba8a0299701eab075f48847cca907 \
- --hash=sha256:2e3484d8455af3fdb0424eae1789af61f6a79da0c80079125112fd5c1b604218 \
- --hash=sha256:34a1fc29f8f96e78ec57a5eff5e8d8b53d3298c3be6df61e7aa9efba26929522 \
- --hash=sha256:3e66497cd990b1a130e21919b004da2f1dc112132c01ac78011a90a0f9229778 \
- --hash=sha256:40e0d7df05e8efe60397c69b467fc8f87a2affeb4d562fe92b72ff8937a2b511 \
- --hash=sha256:456cc8334f6d1124e8ff856b42d2cc1c84335375a16448189999496549f7182b \
- --hash=sha256:506b210cc6e66a0d1c2bb765d055f4f6bc2745070fb1129203b67e85bbfa5c18 \
- --hash=sha256:53273c5487d1c19c3bc03b9eb82adaf8456f243b97ed79d09dded747abaf1235 \
- --hash=sha256:577ed20ec9a18d6bdedb4616f5e9e957b4c08563a9f985563a31fd5b10564d2a \
- --hash=sha256:6803299cbf4665eca14428d9e886de62e24f4223ac31ab9c5d6d5339a39782c7 \
- --hash=sha256:68fa30cec89b6139dc559ed6ef226c53fd80396da1919a1b5ef672c911aaa767 \
- --hash=sha256:6c094e4bfecd2fa7f9adffd03d8abceed7157c928c2976899de282f3600f0a3d \
- --hash=sha256:778d398c4866d8e36ee3bf833779c940b5f57192fa0a549b3ad67bc4c822771b \
- --hash=sha256:7a350ca685d9f594123f652ba796ee37219bf72c8e0fc4b471473d87121d6d34 \
- --hash=sha256:87900c67c0f1728e6db17c6809ec05c025c6624dcf96a8020326ea15378fe8e7 \
- --hash=sha256:8a77906dc2ef9b67407cec0bdbf08e3971141e535db888974a915be5e1e3efc6 \
- --hash=sha256:8e70ae6475cfd0fad3816dcbf6cac536dc6f100f7474be58d59fa306e6e768a4 \
- --hash=sha256:abf67e05a1b7f86583f6ebd01f69b693b9c535276f4e943292e444855870a1b8 \
- --hash=sha256:b04fc29bcef04d4e2d626af28d9d892be6aba94856cb46ed52bcb219ceac8943 \
- --hash=sha256:b19a761b948e939a9e20173aaae76070025f0024fc8f7ba08bef22a5c8573afc \
- --hash=sha256:b2e9810e09c3a47b73ce9cab5a72243a1258f61e7900969097a817232246ce1c \
- --hash=sha256:b71f3a7ca935fc759f2aed7cec06cfe10bc3100fadb5dbd9c435b04e557971e1 \
- --hash=sha256:b8a4fb2a0c5afbe9604f8a91d7d0f27b1832c3e0b5e365f95a13015822b4cd65 \
- --hash=sha256:bb1c613908f11bac270bc7494d68b1ef6e7c224b7a4204d5dacf3522a41e2bc3 \
- --hash=sha256:d24e5bb8028541ce25e59390122f5e48c8506b7e35587e5135efcb6471b4ac6c \
- --hash=sha256:d70a32ee1f8b55eed3fd4e892f0286df8cccc7e0475c11d33b5d0a148f5c7599 \
- --hash=sha256:e293b16cf303fe82995e41700d172a58a15efc5331125d08246b520843ef21ee \
- --hash=sha256:e2f28a07b4f82abb40267864ad7b3a4ed76f1b1663e81c7efc84a9b9248f672f \
- --hash=sha256:e3520a274a0e054e919f5b3279ee5dbccf5311833819ccf3399dab7c83e90a25 \
- --hash=sha256:e3b6f3fd0d8ca37861c31e9a7cab71a0ef14c639b4c95654ea1dd153158bf0df \
- --hash=sha256:e486f60db0cd1c8d68464d9484fd2a94011c1ac8593d765d0211f9daba2bd535 \
- --hash=sha256:e8c87cdaf06fd7b2477f68909838ff4176f105064a72ca9d24d3f2a29f73d393 \
- --hash=sha256:edf5e4e1d5fb22c18820e8586fb867455de3b109c309cb4fce3aaed85d9468d1 \
- --hash=sha256:fe8d40c434a8e2c68d64c6d6a04e77f21791a93ff6afe0dce169597c110d3079
+matplotlib==3.6.2 \
+ --hash=sha256:0844523dfaaff566e39dbfa74e6f6dc42e92f7a365ce80929c5030b84caa563a \
+ --hash=sha256:0eda9d1b43f265da91fb9ae10d6922b5a986e2234470a524e6b18f14095b20d2 \
+ --hash=sha256:168093410b99f647ba61361b208f7b0d64dde1172b5b1796d765cd243cadb501 \
+ --hash=sha256:1836f366272b1557a613f8265db220eb8dd883202bbbabe01bad5a4eadfd0c95 \
+ --hash=sha256:19d61ee6414c44a04addbe33005ab1f87539d9f395e25afcbe9a3c50ce77c65c \
+ --hash=sha256:252957e208c23db72ca9918cb33e160c7833faebf295aaedb43f5b083832a267 \
+ --hash=sha256:32d29c8c26362169c80c5718ce367e8c64f4dd068a424e7110df1dd2ed7bd428 \
+ --hash=sha256:380d48c15ec41102a2b70858ab1dedfa33eb77b2c0982cb65a200ae67a48e9cb \
+ --hash=sha256:3964934731fd7a289a91d315919cf757f293969a4244941ab10513d2351b4e83 \
+ --hash=sha256:3cef89888a466228fc4e4b2954e740ce8e9afde7c4315fdd18caa1b8de58ca17 \
+ --hash=sha256:4426c74761790bff46e3d906c14c7aab727543293eed5a924300a952e1a3a3c1 \
+ --hash=sha256:5024b8ed83d7f8809982d095d8ab0b179bebc07616a9713f86d30cf4944acb73 \
+ --hash=sha256:52c2bdd7cd0bf9d5ccdf9c1816568fd4ccd51a4d82419cc5480f548981b47dd0 \
+ --hash=sha256:54fa9fe27f5466b86126ff38123261188bed568c1019e4716af01f97a12fe812 \
+ --hash=sha256:5ba73aa3aca35d2981e0b31230d58abb7b5d7ca104e543ae49709208d8ce706a \
+ --hash=sha256:5e16dcaecffd55b955aa5e2b8a804379789c15987e8ebd2f32f01398a81e975b \
+ --hash=sha256:5ecfc6559132116dedfc482d0ad9df8a89dc5909eebffd22f3deb684132d002f \
+ --hash=sha256:74153008bd24366cf099d1f1e83808d179d618c4e32edb0d489d526523a94d9f \
+ --hash=sha256:78ec3c3412cf277e6252764ee4acbdbec6920cc87ad65862272aaa0e24381eee \
+ --hash=sha256:795ad83940732b45d39b82571f87af0081c120feff2b12e748d96bb191169e33 \
+ --hash=sha256:7f716b6af94dc1b6b97c46401774472f0867e44595990fe80a8ba390f7a0a028 \
+ --hash=sha256:83dc89c5fd728fdb03b76f122f43b4dcee8c61f1489e232d9ad0f58020523e1c \
+ --hash=sha256:8a0ae37576ed444fe853709bdceb2be4c7df6f7acae17b8378765bd28e61b3ae \
+ --hash=sha256:8a8dbe2cb7f33ff54b16bb5c500673502a35f18ac1ed48625e997d40c922f9cc \
+ --hash=sha256:8a9d899953c722b9afd7e88dbefd8fb276c686c3116a43c577cfabf636180558 \
+ --hash=sha256:8d0068e40837c1d0df6e3abf1cdc9a34a6d2611d90e29610fa1d2455aeb4e2e5 \
+ --hash=sha256:9347cc6822f38db2b1d1ce992f375289670e595a2d1c15961aacbe0977407dfc \
+ --hash=sha256:9f335e5625feb90e323d7e3868ec337f7b9ad88b5d633f876e3b778813021dab \
+ --hash=sha256:b03fd10a1709d0101c054883b550f7c4c5e974f751e2680318759af005964990 \
+ --hash=sha256:b0ca2c60d3966dfd6608f5f8c49b8a0fcf76de6654f2eda55fc6ef038d5a6f27 \
+ --hash=sha256:b2604c6450f9dd2c42e223b1f5dca9643a23cfecc9fde4a94bb38e0d2693b136 \
+ --hash=sha256:ca0e7a658fbafcddcaefaa07ba8dae9384be2343468a8e011061791588d839fa \
+ --hash=sha256:d0e9ac04065a814d4cf2c6791a2ad563f739ae3ae830d716d54245c2b96fead6 \
+ --hash=sha256:d50e8c1e571ee39b5dfbc295c11ad65988879f68009dd281a6e1edbc2ff6c18c \
+ --hash=sha256:d840adcad7354be6f2ec28d0706528b0026e4c3934cc6566b84eac18633eab1b \
+ --hash=sha256:e0bbee6c2a5bf2a0017a9b5e397babb88f230e6f07c3cdff4a4c4bc75ed7c617 \
+ --hash=sha256:e5afe0a7ea0e3a7a257907060bee6724a6002b7eec55d0db16fd32409795f3e1 \
+ --hash=sha256:e68be81cd8c22b029924b6d0ee814c337c0e706b8d88495a617319e5dd5441c3 \
+ --hash=sha256:ec9be0f4826cdb3a3a517509dcc5f87f370251b76362051ab59e42b6b765f8c4 \
+ --hash=sha256:f04f97797df35e442ed09f529ad1235d1f1c0f30878e2fe09a2676b71a8801e0 \
+ --hash=sha256:f41e57ad63d336fe50d3a67bb8eaa26c09f6dda6a59f76777a99b8ccd8e26aec
# via -r tools/python/requirements.txt
mergedeep==1.3.4 \
--hash=sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8 \
--hash=sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307
# via mkdocs
-mkdocs==1.4.0 \
- --hash=sha256:ce057e9992f017b8e1496b591b6c242cbd34c2d406e2f9af6a19b97dd6248faa \
- --hash=sha256:e5549a22d59e7cb230d6a791edd2c3d06690908454c0af82edc31b35d57e3069
+mkdocs==1.4.2 \
+ --hash=sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5 \
+ --hash=sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c
# via -r tools/python/requirements.txt
-numpy==1.21.5 \
- --hash=sha256:00c9fa73a6989895b8815d98300a20ac993c49ac36c8277e8ffeaa3631c0dbbb \
- --hash=sha256:025b497014bc33fc23897859350f284323f32a2fff7654697f5a5fc2a19e9939 \
- --hash=sha256:08de8472d9f7571f9d51b27b75e827f5296295fa78817032e84464be8bb905bc \
- --hash=sha256:1964db2d4a00348b7a60ee9d013c8cb0c566644a589eaa80995126eac3b99ced \
- --hash=sha256:2a9add27d7fc0fdb572abc3b2486eb3b1395da71e0254c5552b2aad2a18b5441 \
- --hash=sha256:2d8adfca843bc46ac199a4645233f13abf2011a0b2f4affc5c37cd552626f27b \
- --hash=sha256:301e408a052fdcda5cdcf03021ebafc3c6ea093021bf9d1aa47c54d48bdad166 \
- --hash=sha256:311283acf880cfcc20369201bd75da907909afc4666966c7895cbed6f9d2c640 \
- --hash=sha256:341dddcfe3b7b6427a28a27baa59af5ad51baa59bfec3264f1ab287aa3b30b13 \
- --hash=sha256:3a5098df115340fb17fc93867317a947e1dcd978c3888c5ddb118366095851f8 \
- --hash=sha256:3c978544be9e04ed12016dd295a74283773149b48f507d69b36f91aa90a643e5 \
- --hash=sha256:3d893b0871322eaa2f8c7072cdb552d8e2b27645b7875a70833c31e9274d4611 \
- --hash=sha256:4fe6a006557b87b352c04596a6e3f12a57d6e5f401d804947bd3188e6b0e0e76 \
- --hash=sha256:507c05c7a37b3683eb08a3ff993bd1ee1e6c752f77c2f275260533b265ecdb6c \
- --hash=sha256:58ca1d7c8aef6e996112d0ce873ac9dfa1eaf4a1196b4ff7ff73880a09923ba7 \
- --hash=sha256:61bada43d494515d5b122f4532af226fdb5ee08fe5b5918b111279843dc6836a \
- --hash=sha256:69a5a8d71c308d7ef33ef72371c2388a90e3495dbb7993430e674006f94797d5 \
- --hash=sha256:6a5928bc6241264dce5ed509e66f33676fc97f464e7a919edc672fb5532221ee \
- --hash=sha256:7b9d6b14fc9a4864b08d1ba57d732b248f0e482c7b2ff55c313137e3ed4d8449 \
- --hash=sha256:a7c4b701ca418cd39e28ec3b496e6388fe06de83f5f0cb74794fa31cfa384c02 \
- --hash=sha256:a7e8f6216f180f3fd4efb73de5d1eaefb5f5a1ee5b645c67333033e39440e63a \
- --hash=sha256:b545ebadaa2b878c8630e5bcdb97fc4096e779f335fc0f943547c1c91540c815 \
- --hash=sha256:c293d3c0321996cd8ffe84215ffe5d269fd9d1d12c6f4ffe2b597a7c30d3e593 \
- --hash=sha256:c5562bcc1a9b61960fc8950ade44d00e3de28f891af0acc96307c73613d18f6e \
- --hash=sha256:ca9c23848292c6fe0a19d212790e62f398fd9609aaa838859be8459bfbe558aa \
- --hash=sha256:cc1b30205d138d1005adb52087ff45708febbef0e420386f58664f984ef56954 \
- --hash=sha256:dbce7adeb66b895c6aaa1fad796aaefc299ced597f6fbd9ceddb0dd735245354 \
- --hash=sha256:dc4b2fb01f1b4ddbe2453468ea0719f4dbb1f5caa712c8b21bb3dd1480cd30d9 \
- --hash=sha256:eed2afaa97ec33b4411995be12f8bdb95c87984eaa28d76cf628970c8a2d689a \
- --hash=sha256:fc7a7d7b0ed72589fd8b8486b9b42a564f10b8762be8bd4d9df94b807af4a089
+numpy==1.23.5 \
+ --hash=sha256:01dd17cbb340bf0fc23981e52e1d18a9d4050792e8fb8363cecbf066a84b827d \
+ --hash=sha256:06005a2ef6014e9956c09ba07654f9837d9e26696a0470e42beedadb78c11b07 \
+ --hash=sha256:09b7847f7e83ca37c6e627682f145856de331049013853f344f37b0c9690e3df \
+ --hash=sha256:0aaee12d8883552fadfc41e96b4c82ee7d794949e2a7c3b3a7201e968c7ecab9 \
+ --hash=sha256:0cbe9848fad08baf71de1a39e12d1b6310f1d5b2d0ea4de051058e6e1076852d \
+ --hash=sha256:1b1766d6f397c18153d40015ddfc79ddb715cabadc04d2d228d4e5a8bc4ded1a \
+ --hash=sha256:33161613d2269025873025b33e879825ec7b1d831317e68f4f2f0f84ed14c719 \
+ --hash=sha256:5039f55555e1eab31124a5768898c9e22c25a65c1e0037f4d7c495a45778c9f2 \
+ --hash=sha256:522e26bbf6377e4d76403826ed689c295b0b238f46c28a7251ab94716da0b280 \
+ --hash=sha256:56e454c7833e94ec9769fa0f86e6ff8e42ee38ce0ce1fa4cbb747ea7e06d56aa \
+ --hash=sha256:58f545efd1108e647604a1b5aa809591ccd2540f468a880bedb97247e72db387 \
+ --hash=sha256:5e05b1c973a9f858c74367553e236f287e749465f773328c8ef31abe18f691e1 \
+ --hash=sha256:7903ba8ab592b82014713c491f6c5d3a1cde5b4a3bf116404e08f5b52f6daf43 \
+ --hash=sha256:8969bfd28e85c81f3f94eb4a66bc2cf1dbdc5c18efc320af34bffc54d6b1e38f \
+ --hash=sha256:92c8c1e89a1f5028a4c6d9e3ccbe311b6ba53694811269b992c0b224269e2398 \
+ --hash=sha256:9c88793f78fca17da0145455f0d7826bcb9f37da4764af27ac945488116efe63 \
+ --hash=sha256:a7ac231a08bb37f852849bbb387a20a57574a97cfc7b6cabb488a4fc8be176de \
+ --hash=sha256:abdde9f795cf292fb9651ed48185503a2ff29be87770c3b8e2a14b0cd7aa16f8 \
+ --hash=sha256:af1da88f6bc3d2338ebbf0e22fe487821ea4d8e89053e25fa59d1d79786e7481 \
+ --hash=sha256:b2a9ab7c279c91974f756c84c365a669a887efa287365a8e2c418f8b3ba73fb0 \
+ --hash=sha256:bf837dc63ba5c06dc8797c398db1e223a466c7ece27a1f7b5232ba3466aafe3d \
+ --hash=sha256:ca51fcfcc5f9354c45f400059e88bc09215fb71a48d3768fb80e357f3b457e1e \
+ --hash=sha256:ce571367b6dfe60af04e04a1834ca2dc5f46004ac1cc756fb95319f64c095a96 \
+ --hash=sha256:d208a0f8729f3fb790ed18a003f3a57895b989b40ea4dce4717e9cf4af62c6bb \
+ --hash=sha256:dbee87b469018961d1ad79b1a5d50c0ae850000b639bcb1b694e9981083243b6 \
+ --hash=sha256:e9f4c4e51567b616be64e05d517c79a8a22f3606499941d97bb76f2ca59f982d \
+ --hash=sha256:f063b69b090c9d918f9df0a12116029e274daf0181df392839661c4c7ec9018a \
+ --hash=sha256:f9a909a8bae284d46bbfdefbdd4a262ba19d3bc9921b1e76126b1d21c3c34135
# via
# -r tools/python/requirements.txt
+ # contourpy
# matplotlib
# opencv-python
# osqp
@@ -228,23 +328,32 @@
--hash=sha256:e6e448b62afc95c5b58f97e87ef84699e6607fe5c58730a03301c52496005cae \
--hash=sha256:f482e78de6e7b0b060ff994ffd859bddc3f7f382bb2019ef157b0ea8ca8712f5
# via -r tools/python/requirements.txt
-osqp==0.6.2.post5 \
- --hash=sha256:26664bd4238f0f92642f532b23e61efba810a6debba0b3117300749f801e9c25 \
- --hash=sha256:4ca601c5008600b3e0a408339be21f9d626c497b0b0c4dbe4ffe6d6dbbed1b9f \
- --hash=sha256:51a315e02a4cb42e1911047ec6b2a44b67a269d4b5d37d7ee737654206915c82 \
- --hash=sha256:648cb4e34caf0ee948b34a1d0b184f5233e30009090884e0d75503f868bf7b1f \
- --hash=sha256:73a307a93fa7ab68b610e08637c95940070a27f11fda5a2e7a7095cfaff3f0ef \
- --hash=sha256:77408f93ed261581fe498505c69480fb8584c8c0da2a2cd0710bb4bae0c872f5 \
- --hash=sha256:8003fc363f707daa46fef3af548e6a580372154d6cd49a7bf2f569ba5f807d15 \
- --hash=sha256:8c2e40e6788b860887d584a9929ad1c0e436aab8f82bb24da7b165034cb04017 \
- --hash=sha256:908d42fb5d1d9cb36d74a8f3db69384ed1813f1a3e755367557395ce7cf05e16 \
- --hash=sha256:b1e30d6fa10ed11a95023d7308ec1588de3f5b049d09a4d0cc49e057f8e9ce47 \
- --hash=sha256:b2fa17aae42a7ed498ec261b33f262bb4b3605e7e8464062159d9fae817f0d61 \
- --hash=sha256:c07602c8747ce7a177d091bb6d47ce8f214997a86b7577ddee4adae43e9ac92f \
- --hash=sha256:c23bb95e6f72c6b253737edb9e4ef47ceccc3d891c287041ed5fe5f173d317bb \
- --hash=sha256:c7b3ae95221ad6f607dc4a69f36b7a0c71ca434ce85dcbf5cfa084770be5b249 \
- --hash=sha256:c9470c5d58535d31080cb693568916a3e837f09dfa94819a85284b36b3626738 \
- --hash=sha256:ff71646bc9d55c5b3a72cc9b4197e51c36d25d8b2bb81f975d3ce7772ff188ec
+osqp==0.6.2.post8 \
+ --hash=sha256:02175818a0b1715ae0aab88a23678a44b269587af0ef655457042ca69a45eddd \
+ --hash=sha256:0a6e36151d088a9196b24fffc6b1d3a8bf79dcf9e7a5bd5f9c76c9ee1e019edf \
+ --hash=sha256:1d635a321686d15aaf2d91b05f41f736333d6adb0639bc14fc1c22b2cfce9c80 \
+ --hash=sha256:1ecbd173c21805b64a0b736d051312241a84327759526505578f83f7dcc81c66 \
+ --hash=sha256:22724b3ac4eaf17582e3ff35cb6660c026e71138f27fc21dbae4f1dc60904c64 \
+ --hash=sha256:23d6bae4a3612f60d5f652d0e5fa4b2ead507cabfff5d930d822057ae6ed6677 \
+ --hash=sha256:2cc3a966afc4c6ef29dbeb92c59aec7479451149bb77f5c318767433da2c1863 \
+ --hash=sha256:2d39020616c8b4fd9b3ec11f96bd3d68f366ab161323ecb9c1f9c7024eda2d28 \
+ --hash=sha256:2f8647e63bba38f57161d80dda251c06c290bb99e4767cc58a37727ee3c8b912 \
+ --hash=sha256:470c07e7dd06588576155133ae9aea62077dbaa4310aa8e387e879403de42369 \
+ --hash=sha256:497a2fb0d14d20185eaa32aa5f98374fe9a57df09ed0aedb2c27c37d0aa54afa \
+ --hash=sha256:52daa25502056aa1643e2d23ee230a7fe1c399e1a8b35a7b5dd2b77c7b356007 \
+ --hash=sha256:58b38557b0a6181dff8f557244758b955ff27384a1f67b83d75e51fd34c9e842 \
+ --hash=sha256:6a009c100eaaf93e9b2b790af61e209090d2a60b629893e21052d7216e572bbe \
+ --hash=sha256:7f888eaa54bac0261cadb145b3bcf8b2da9109cbf53fc4fdbdc6c6f6c04e2bb9 \
+ --hash=sha256:866f1bc2386b15393a68d379447808bbf3c8b2a126b0fc0669b27fcf3985b86c \
+ --hash=sha256:8d4920fb588d861d0d92874cb5b4435db16fe1e36a986d30638106afe374c1a8 \
+ --hash=sha256:ac9c6aaebe56eae33d7545564148a8fab1d71117cbbe0eedbd2c658bc3455df9 \
+ --hash=sha256:b30e7a2f49103622fdad9ed9c127c47afae01f5a8a6994d04803d3d5deadab4e \
+ --hash=sha256:bd956b7af9d524aed60ab41ec47b20519aede28538dea8f3188ad9056c4c0b01 \
+ --hash=sha256:c9705647d7e6171b3baaa68b0c159c43ea69cba22fbdbd8f79f86ae404a3d96f \
+ --hash=sha256:dd4b2ee44ec08253bcafb4d8a45c7d8278caa0bc13ac7ed24aa35249da7f1d2a \
+ --hash=sha256:dea8085760268971985bb3366bf4d5fb2e8291d7013c47e6178abb964cf05b86 \
+ --hash=sha256:e2475e1417e0ff86b5cd363d9dc2796d54f2a42f67a95fc527eb2ed15df6a1ac \
+ --hash=sha256:f30b405ec0e6a2acf52f59e04f1c258480be172f64c2d37c24adcbf2ac400548
# via -r tools/python/requirements.txt
packaging==21.3 \
--hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \
@@ -252,70 +361,92 @@
# via
# matplotlib
# mkdocs
-pillow==8.4.0 \
- --hash=sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76 \
- --hash=sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585 \
- --hash=sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b \
- --hash=sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8 \
- --hash=sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55 \
- --hash=sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc \
- --hash=sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645 \
- --hash=sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff \
- --hash=sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc \
- --hash=sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b \
- --hash=sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6 \
- --hash=sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20 \
- --hash=sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e \
- --hash=sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a \
- --hash=sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779 \
- --hash=sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02 \
- --hash=sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39 \
- --hash=sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f \
- --hash=sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a \
- --hash=sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409 \
- --hash=sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c \
- --hash=sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488 \
- --hash=sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b \
- --hash=sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d \
- --hash=sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09 \
- --hash=sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b \
- --hash=sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153 \
- --hash=sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9 \
- --hash=sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad \
- --hash=sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df \
- --hash=sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df \
- --hash=sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed \
- --hash=sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed \
- --hash=sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698 \
- --hash=sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29 \
- --hash=sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649 \
- --hash=sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49 \
- --hash=sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b \
- --hash=sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2 \
- --hash=sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a \
- --hash=sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78
+pillow==9.3.0 \
+ --hash=sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040 \
+ --hash=sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8 \
+ --hash=sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65 \
+ --hash=sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2 \
+ --hash=sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627 \
+ --hash=sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07 \
+ --hash=sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef \
+ --hash=sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535 \
+ --hash=sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c \
+ --hash=sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc \
+ --hash=sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3 \
+ --hash=sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1 \
+ --hash=sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c \
+ --hash=sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa \
+ --hash=sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32 \
+ --hash=sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502 \
+ --hash=sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4 \
+ --hash=sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f \
+ --hash=sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812 \
+ --hash=sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636 \
+ --hash=sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20 \
+ --hash=sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c \
+ --hash=sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91 \
+ --hash=sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe \
+ --hash=sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b \
+ --hash=sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad \
+ --hash=sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9 \
+ --hash=sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72 \
+ --hash=sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4 \
+ --hash=sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de \
+ --hash=sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29 \
+ --hash=sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee \
+ --hash=sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c \
+ --hash=sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7 \
+ --hash=sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11 \
+ --hash=sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c \
+ --hash=sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c \
+ --hash=sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448 \
+ --hash=sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b \
+ --hash=sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20 \
+ --hash=sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228 \
+ --hash=sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd \
+ --hash=sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699 \
+ --hash=sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b \
+ --hash=sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2 \
+ --hash=sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4 \
+ --hash=sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c \
+ --hash=sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f \
+ --hash=sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2 \
+ --hash=sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c \
+ --hash=sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3 \
+ --hash=sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193 \
+ --hash=sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48 \
+ --hash=sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02 \
+ --hash=sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8 \
+ --hash=sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e \
+ --hash=sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f \
+ --hash=sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b \
+ --hash=sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74 \
+ --hash=sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb \
+ --hash=sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0
# via matplotlib
pkginfo==1.8.3 \
--hash=sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594 \
--hash=sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c
# via -r tools/python/requirements.txt
-pycairo==1.21.0 \
- --hash=sha256:251907f18a552df938aa3386657ff4b5a4937dde70e11aa042bc297957f4b74b \
- --hash=sha256:26b72b813c6f9d495f71057eab89c13e70a21c92360e9265abc049e0a931fa39 \
- --hash=sha256:31e1c4850db03201d33929cbe1905ce1b33202683ebda7bb0d4dba489115066b \
- --hash=sha256:4357f20a6b1de8f1e8072a74ff68ab4c9a0ae698cd9f5c0f2b2cdd9b28b635f6 \
- --hash=sha256:44a2ecf34968de07b3b9dfdcdbccbd25aa3cab267200f234f84e81481a73bbf6 \
- --hash=sha256:6d37375aab9f2bb6136f076c19815d72108383baae89fbc0d6cb8e5092217d02 \
- --hash=sha256:70936b19f967fa3cb3cd200c2608911227fa5d09dae21c166f64bc15e714ee41 \
- --hash=sha256:dace6b356c476de27f8e1522428ac21a799c225703f746e2957d441f885dcb6c \
- --hash=sha256:f63c153a9ea3d21aff85e2caeee4b0c5d566b2368b4ed64826020d12953d76a4
+pycairo==1.22.0 \
+ --hash=sha256:007ae728c56b9a0962d8c5513ae967a4fceff03e022940383c20f4f3d4c48dbe \
+ --hash=sha256:00c8a6b92c5075ee3be7ea1d33f676d259f11f92cad7e37077dd193437c8c27c \
+ --hash=sha256:356c9fc665e8522f497b6cbe026ad8decacbb04c93e13fd5d145956433f3d471 \
+ --hash=sha256:47aed13e950345c8248f77c8a51bff52188bef7afd3d5169584e0eddc21ba341 \
+ --hash=sha256:5a62cf1d2c6339028709a600d83c0c24111feedeef3cf977bca333fbb94a79c8 \
+ --hash=sha256:62ce5e8c97eeee70170ba9a74845a0ded4bde9b7f1701d88957cbadf8cb1ccd6 \
+ --hash=sha256:9fbe26b3fbe85fde063070e543b4a5f3609569ca8f79680867cecb837d5be29c \
+ --hash=sha256:b34517abdf619d4c7f0274f012b398d9b03bab7adc3efd2912bf36be3f911f3f \
+ --hash=sha256:b85807ec65a8b7966aca7aa41c39016b72515d6401a874a4b52c314471b31865 \
+ --hash=sha256:e31a5b70664c425f4d1b71ba8aaf259920de6937a9490132ffabadad2a89764f \
+ --hash=sha256:e81189414c11340134bffa6dcb06a378976cb87a6742f39aaefc79cb27612250
# via pygobject
pygobject==3.42.2 \
--hash=sha256:21524cef33100c8fd59dc135948b703d79d303e368ce71fa60521cc971cd8aa7
# via -r tools/python/requirements.txt
-pyparsing==3.0.6 \
- --hash=sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4 \
- --hash=sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81
+pyparsing==3.0.9 \
+ --hash=sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb \
+ --hash=sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc
# via
# matplotlib
# packaging
@@ -395,36 +526,28 @@
--hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \
--hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349
# via -r tools/python/requirements.txt
-scipy==1.7.3 \
- --hash=sha256:033ce76ed4e9f62923e1f8124f7e2b0800db533828c853b402c7eec6e9465d80 \
- --hash=sha256:173308efba2270dcd61cd45a30dfded6ec0085b4b6eb33b5eb11ab443005e088 \
- --hash=sha256:21b66200cf44b1c3e86495e3a436fc7a26608f92b8d43d344457c54f1c024cbc \
- --hash=sha256:2c56b820d304dffcadbbb6cbfbc2e2c79ee46ea291db17e288e73cd3c64fefa9 \
- --hash=sha256:304dfaa7146cffdb75fbf6bb7c190fd7688795389ad060b970269c8576d038e9 \
- --hash=sha256:3f78181a153fa21c018d346f595edd648344751d7f03ab94b398be2ad083ed3e \
- --hash=sha256:4d242d13206ca4302d83d8a6388c9dfce49fc48fdd3c20efad89ba12f785bf9e \
- --hash=sha256:5d1cc2c19afe3b5a546ede7e6a44ce1ff52e443d12b231823268019f608b9b12 \
- --hash=sha256:5f2cfc359379c56b3a41b17ebd024109b2049f878badc1e454f31418c3a18436 \
- --hash=sha256:65bd52bf55f9a1071398557394203d881384d27b9c2cad7df9a027170aeaef93 \
- --hash=sha256:7edd9a311299a61e9919ea4192dd477395b50c014cdc1a1ac572d7c27e2207fa \
- --hash=sha256:8499d9dd1459dc0d0fe68db0832c3d5fc1361ae8e13d05e6849b358dc3f2c279 \
- --hash=sha256:866ada14a95b083dd727a845a764cf95dd13ba3dc69a16b99038001b05439709 \
- --hash=sha256:87069cf875f0262a6e3187ab0f419f5b4280d3dcf4811ef9613c605f6e4dca95 \
- --hash=sha256:93378f3d14fff07572392ce6a6a2ceb3a1f237733bd6dcb9eb6a2b29b0d19085 \
- --hash=sha256:95c2d250074cfa76715d58830579c64dff7354484b284c2b8b87e5a38321672c \
- --hash=sha256:ab5875facfdef77e0a47d5fd39ea178b58e60e454a4c85aa1e52fcb80db7babf \
- --hash=sha256:b0e0aeb061a1d7dcd2ed59ea57ee56c9b23dd60100825f98238c06ee5cc4467e \
- --hash=sha256:b78a35c5c74d336f42f44106174b9851c783184a85a3fe3e68857259b37b9ffb \
- --hash=sha256:c9e04d7e9b03a8a6ac2045f7c5ef741be86727d8f49c45db45f244bdd2bcff17 \
- --hash=sha256:ca36e7d9430f7481fc7d11e015ae16fbd5575615a8e9060538104778be84addf \
- --hash=sha256:ceebc3c4f6a109777c0053dfa0282fddb8893eddfb0d598574acfb734a926168 \
- --hash=sha256:e2c036492e673aad1b7b0d0ccdc0cb30a968353d2c4bf92ac8e73509e1bf212c \
- --hash=sha256:eb326658f9b73c07081300daba90a8746543b5ea177184daed26528273157294 \
- --hash=sha256:eb7ae2c4dbdb3c9247e07acc532f91077ae6dbc40ad5bd5dca0bb5a176ee9bda \
- --hash=sha256:edad1cf5b2ce1912c4d8ddad20e11d333165552aba262c882e28c78bbc09dbf6 \
- --hash=sha256:eef93a446114ac0193a7b714ce67659db80caf940f3232bad63f4c7a81bc18df \
- --hash=sha256:f7eaea089345a35130bc9a39b89ec1ff69c208efa97b3f8b25ea5d4c41d88094 \
- --hash=sha256:f99d206db1f1ae735a8192ab93bd6028f3a42f6fa08467d37a14eb96c9dd34a3
+scipy==1.9.3 \
+ --hash=sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31 \
+ --hash=sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108 \
+ --hash=sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0 \
+ --hash=sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b \
+ --hash=sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e \
+ --hash=sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e \
+ --hash=sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5 \
+ --hash=sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840 \
+ --hash=sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58 \
+ --hash=sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523 \
+ --hash=sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd \
+ --hash=sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab \
+ --hash=sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c \
+ --hash=sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb \
+ --hash=sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096 \
+ --hash=sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0 \
+ --hash=sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc \
+ --hash=sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9 \
+ --hash=sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c \
+ --hash=sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95 \
+ --hash=sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027
# via
# -r tools/python/requirements.txt
# osqp
@@ -433,9 +556,9 @@
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via python-dateutil
-urllib3==1.26.12 \
- --hash=sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e \
- --hash=sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997
+urllib3==1.26.13 \
+ --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
+ --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
# via requests
watchdog==2.1.9 \
--hash=sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412 \
@@ -464,7 +587,11 @@
--hash=sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9 \
--hash=sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658
# via mkdocs
-zipp==3.8.1 \
- --hash=sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2 \
- --hash=sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009
+yapf==0.32.0 \
+ --hash=sha256:8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32 \
+ --hash=sha256:a3f5085d37ef7e3e004c4ba9f9b3e40c54ff1901cd111f05145ae313a7c67d1b
+ # via -r tools/python/requirements.txt
+zipp==3.11.0 \
+ --hash=sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa \
+ --hash=sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766
# via importlib-metadata
diff --git a/tools/python/requirements.txt b/tools/python/requirements.txt
index 9bbecbf..a4cbd6a 100644
--- a/tools/python/requirements.txt
+++ b/tools/python/requirements.txt
@@ -11,3 +11,4 @@
pygobject
requests
scipy
+yapf
diff --git a/tools/python/whl_overrides.json b/tools/python/whl_overrides.json
index 81c4701..3d724b9 100644
--- a/tools/python/whl_overrides.json
+++ b/tools/python/whl_overrides.json
@@ -1,7 +1,7 @@
{
- "certifi==2022.9.14": {
- "sha256": "e232343de1ab72c2aa521b625c80f699e356830fd0e2c620b465b304b17b0516",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/certifi-2022.9.14-py3-none-any.whl"
+ "certifi==2022.9.24": {
+ "sha256": "90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/certifi-2022.9.24-py3-none-any.whl"
},
"charset_normalizer==2.1.1": {
"sha256": "83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f",
@@ -11,13 +11,17 @@
"sha256": "bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/click-8.1.3-py3-none-any.whl"
},
+ "contourpy==1.0.6": {
+ "sha256": "1dedf4c64185a216c35eb488e6f433297c660321275734401760dafaeb0ad5c2",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/contourpy-1.0.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
+ },
"cycler==0.11.0": {
"sha256": "3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/cycler-0.11.0-py3-none-any.whl"
},
- "fonttools==4.28.5": {
- "sha256": "edf251d5d2cc0580d5f72de4621c338d8c66c5f61abb50cf486640f73c8194d5",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/fonttools-4.28.5-py3-none-any.whl"
+ "fonttools==4.38.0": {
+ "sha256": "820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/fonttools-4.38.0-py3-none-any.whl"
},
"ghp_import==2.1.0": {
"sha256": "8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619",
@@ -27,17 +31,17 @@
"sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/idna-3.4-py3-none-any.whl"
},
- "importlib_metadata==5.0.0": {
- "sha256": "ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/importlib_metadata-5.0.0-py3-none-any.whl"
+ "importlib_metadata==5.1.0": {
+ "sha256": "d84d17e21670ec07990e1044a99efe8d615d860fd176fc29ef5c306068fda313",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/importlib_metadata-5.1.0-py3-none-any.whl"
},
"jinja2==3.1.2": {
"sha256": "6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/Jinja2-3.1.2-py3-none-any.whl"
},
- "kiwisolver==1.3.2": {
- "sha256": "30fa008c172355c7768159983a7270cb23838c4d7db73d6c0f6b60dde0d432c6",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/kiwisolver-1.3.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
+ "kiwisolver==1.4.4": {
+ "sha256": "7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
},
"markdown==3.3.7": {
"sha256": "f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621",
@@ -47,53 +51,53 @@
"sha256": "56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
- "matplotlib==3.5.1": {
- "sha256": "87900c67c0f1728e6db17c6809ec05c025c6624dcf96a8020326ea15378fe8e7",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/matplotlib-3.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl"
+ "matplotlib==3.6.2": {
+ "sha256": "795ad83940732b45d39b82571f87af0081c120feff2b12e748d96bb191169e33",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/matplotlib-3.6.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
"mergedeep==1.3.4": {
"sha256": "70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/mergedeep-1.3.4-py3-none-any.whl"
},
- "mkdocs==1.4.0": {
- "sha256": "ce057e9992f017b8e1496b591b6c242cbd34c2d406e2f9af6a19b97dd6248faa",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/mkdocs-1.4.0-py3-none-any.whl"
+ "mkdocs==1.4.2": {
+ "sha256": "c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/mkdocs-1.4.2-py3-none-any.whl"
},
- "numpy==1.21.5": {
- "sha256": "c293d3c0321996cd8ffe84215ffe5d269fd9d1d12c6f4ffe2b597a7c30d3e593",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/numpy-1.21.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
+ "numpy==1.23.5": {
+ "sha256": "33161613d2269025873025b33e879825ec7b1d831317e68f4f2f0f84ed14c719",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/numpy-1.23.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
"opencv_python==4.6.0.66": {
"sha256": "dbdc84a9b4ea2cbae33861652d25093944b9959279200b7ae0badd32439f74de",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/opencv_python-4.6.0.66-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
- "osqp==0.6.2.post5": {
- "sha256": "8003fc363f707daa46fef3af548e6a580372154d6cd49a7bf2f569ba5f807d15",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/osqp-0.6.2.post5-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
+ "osqp==0.6.2.post8": {
+ "sha256": "22724b3ac4eaf17582e3ff35cb6660c026e71138f27fc21dbae4f1dc60904c64",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/osqp-0.6.2.post8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
"packaging==21.3": {
"sha256": "ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/packaging-21.3-py3-none-any.whl"
},
- "pillow==8.4.0": {
- "sha256": "b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/Pillow-8.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
+ "pillow==9.3.0": {
+ "sha256": "97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl"
},
"pkginfo==1.8.3": {
"sha256": "848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/pkginfo-1.8.3-py2.py3-none-any.whl"
},
- "pycairo==1.21.0": {
- "sha256": "c1fc681494d470c6af4864991ea406d1344680af69e060af06f7e8391c756ac0",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/pycairo-1.21.0-cp39-cp39-manylinux_2_31_x86_64.whl"
+ "pycairo==1.22.0": {
+ "sha256": "6d8325547b2ee5476d317045ca5824901309cc5444dced73bd7d1262b3e18b83",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/pycairo-1.22.0-cp39-cp39-manylinux_2_31_x86_64.whl"
},
"pygobject==3.42.2": {
- "sha256": "0ccbc4a4d8e3697a060fcff16f7c28780b429052e63277ab4efd78ae2ff0b110",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/PyGObject-3.42.2-cp39-cp39-manylinux_2_31_x86_64.whl"
+ "sha256": "c11807320f696b07525b97800570e80a6563a649f2950d66501e13474e5c3a36",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/PyGObject-3.42.2-cp39-cp39-linux_x86_64.whl"
},
- "pyparsing==3.0.6": {
- "sha256": "04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/pyparsing-3.0.6-py3-none-any.whl"
+ "pyparsing==3.0.9": {
+ "sha256": "5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/pyparsing-3.0.9-py3-none-any.whl"
},
"python_dateutil==2.8.2": {
"sha256": "961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9",
@@ -115,24 +119,28 @@
"sha256": "8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/requests-2.28.1-py3-none-any.whl"
},
- "scipy==1.7.3": {
- "sha256": "5d1cc2c19afe3b5a546ede7e6a44ce1ff52e443d12b231823268019f608b9b12",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/scipy-1.7.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
+ "scipy==1.9.3": {
+ "sha256": "c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/scipy-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
},
"six==1.16.0": {
"sha256": "8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/six-1.16.0-py2.py3-none-any.whl"
},
- "urllib3==1.26.12": {
- "sha256": "b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/urllib3-1.26.12-py2.py3-none-any.whl"
+ "urllib3==1.26.13": {
+ "sha256": "47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/urllib3-1.26.13-py2.py3-none-any.whl"
},
"watchdog==2.1.9": {
"sha256": "4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6",
"url": "https://software.frc971.org/Build-Dependencies/wheelhouse/watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl"
},
- "zipp==3.8.1": {
- "sha256": "47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009",
- "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/zipp-3.8.1-py3-none-any.whl"
+ "yapf==0.32.0": {
+ "sha256": "8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/yapf-0.32.0-py2.py3-none-any.whl"
+ },
+ "zipp==3.11.0": {
+ "sha256": "83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa",
+ "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/zipp-3.11.0-py3-none-any.whl"
}
}
diff --git a/y2020/vision/calibration.cc b/y2020/vision/calibration.cc
index b5d1b32..c6bacdb 100644
--- a/y2020/vision/calibration.cc
+++ b/y2020/vision/calibration.cc
@@ -38,11 +38,21 @@
event_loop, pi,
[this](cv::Mat rgb_image,
const aos::monotonic_clock::time_point eof,
- std::vector<int> charuco_ids,
- std::vector<cv::Point2f> charuco_corners, bool valid,
- Eigen::Vector3d rvec_eigen, Eigen::Vector3d tvec_eigen) {
+ std::vector<cv::Vec4i> charuco_ids,
+ std::vector<std::vector<cv::Point2f>> charuco_corners,
+ bool valid, std::vector<Eigen::Vector3d> rvecs_eigen,
+ std::vector<Eigen::Vector3d> tvecs_eigen) {
HandleCharuco(rgb_image, eof, charuco_ids, charuco_corners, valid,
- rvec_eigen, tvec_eigen);
+ rvecs_eigen, tvecs_eigen);
+ }),
+ image_callback_(
+ event_loop,
+ absl::StrCat(
+ "/pi", std::to_string(aos::network::ParsePiNumber(pi).value()),
+ "/camera"),
+ [this](cv::Mat rgb_image,
+ const aos::monotonic_clock::time_point eof) {
+ charuco_extractor_.HandleImage(rgb_image, eof);
}) {
CHECK(pi_number_) << ": Invalid pi number " << pi
<< ", failed to parse pi number";
@@ -50,13 +60,16 @@
CHECK(std::regex_match(camera_id_, re))
<< ": Invalid camera_id '" << camera_id_
<< "', should be of form YY-NN";
+ CHECK_EQ(FLAGS_target_type, "charuco")
+ << "Intrinsic calibration only works with Charuco board";
}
void HandleCharuco(cv::Mat rgb_image,
const aos::monotonic_clock::time_point /*eof*/,
- std::vector<int> charuco_ids,
- std::vector<cv::Point2f> charuco_corners, bool valid,
- Eigen::Vector3d rvec_eigen, Eigen::Vector3d tvec_eigen) {
+ std::vector<cv::Vec4i> charuco_ids,
+ std::vector<std::vector<cv::Point2f>> charuco_corners,
+ bool valid, std::vector<Eigen::Vector3d> rvecs_eigen,
+ std::vector<Eigen::Vector3d> tvecs_eigen) {
// Reduce resolution displayed on remote viewer to prevent lag
cv::resize(rgb_image, rgb_image,
cv::Size(rgb_image.cols / 2, rgb_image.rows / 2));
@@ -78,12 +91,17 @@
if (!valid) {
return;
}
+ CHECK(tvecs_eigen.size() == 1)
+ << "Charuco board should only return one translational pose";
+ CHECK(rvecs_eigen.size() == 1)
+ << "Charuco board should only return one rotational pose";
// Calibration calculates rotation and translation delta from last image
// stored to automatically capture next image
Eigen::Affine3d H_board_camera =
- Eigen::Translation3d(tvec_eigen) *
- Eigen::AngleAxisd(rvec_eigen.norm(), rvec_eigen / rvec_eigen.norm());
+ Eigen::Translation3d(tvecs_eigen[0]) *
+ Eigen::AngleAxisd(rvecs_eigen[0].norm(),
+ rvecs_eigen[0] / rvecs_eigen[0].norm());
Eigen::Affine3d H_camera_board_ = H_board_camera.inverse();
Eigen::Affine3d H_delta = H_board_camera * prev_H_camera_board_;
@@ -97,8 +115,8 @@
bool store_image = false;
double percent_motion =
std::max<double>(r_norm / kDeltaRThreshold, t_norm / kDeltaTThreshold);
- LOG(INFO) << all_charuco_ids_.size() << ": Moved " << percent_motion
- << "% of what's needed";
+ LOG(INFO) << "Captured: " << all_charuco_ids_.size() << " points; Moved "
+ << percent_motion << "% of what's needed";
// Verify that camera has moved enough from last stored image
if (r_norm > kDeltaRThreshold || t_norm > kDeltaTThreshold) {
// frame_ refers to deltas between current and last captured image
@@ -118,9 +136,10 @@
frame_r_norm < kFrameDeltaRLimit && frame_t_norm < kFrameDeltaTLimit;
double percent_stop = std::max<double>(frame_r_norm / kFrameDeltaRLimit,
frame_t_norm / kFrameDeltaTLimit);
- LOG(INFO) << all_charuco_ids_.size() << ": Moved enough ("
- << percent_motion << "%); Need to stop (last motion was "
- << percent_stop << "%";
+ LOG(INFO) << "Captured: " << all_charuco_ids_.size()
+ << "points; Moved enough (" << percent_motion
+ << "%); Need to stop (last motion was " << percent_stop
+ << "% of limit; needs to be < 1 to capture)";
}
prev_image_H_camera_board_ = H_camera_board_;
@@ -128,8 +147,13 @@
if (valid) {
prev_H_camera_board_ = H_camera_board_;
- all_charuco_ids_.emplace_back(std::move(charuco_ids));
- all_charuco_corners_.emplace_back(std::move(charuco_corners));
+ // Unpack the Charuco ids from Vec4i
+ std::vector<int> charuco_ids_int;
+ for (cv::Vec4i charuco_id : charuco_ids) {
+ charuco_ids_int.emplace_back(charuco_id[0]);
+ }
+ all_charuco_ids_.emplace_back(std::move(charuco_ids_int));
+ all_charuco_corners_.emplace_back(std::move(charuco_corners[0]));
if (r_norm > kDeltaRThreshold) {
LOG(INFO) << "Triggered by rotation delta = " << r_norm << " > "
@@ -164,7 +188,8 @@
img_size, cameraMatrix, distCoeffs, rvecs, tvecs,
stdDeviationsIntrinsics, stdDeviationsExtrinsics, perViewErrors,
calibration_flags);
- CHECK_LE(reprojection_error, 1.0) << ": Reproduction error is bad.";
+ CHECK_LE(reprojection_error, 1.0)
+ << ": Reproduction error is bad-- greater than 1 pixel.";
LOG(INFO) << "Reprojection Error is " << reprojection_error;
flatbuffers::FlatBufferBuilder fbb;
@@ -246,6 +271,7 @@
Eigen::Affine3d prev_image_H_camera_board_;
CharucoExtractor charuco_extractor_;
+ ImageCallback image_callback_;
};
namespace {
diff --git a/y2020/vision/extrinsics_calibration.cc b/y2020/vision/extrinsics_calibration.cc
index c561652..cf0c8f2 100644
--- a/y2020/vision/extrinsics_calibration.cc
+++ b/y2020/vision/extrinsics_calibration.cc
@@ -9,6 +9,7 @@
#include "aos/time/time.h"
#include "aos/util/file.h"
#include "frc971/control_loops/quaternion_utils.h"
+#include "frc971/vision/charuco_lib.h"
#include "frc971/vision/vision_generated.h"
#include "frc971/wpilib/imu_batch_generated.h"
#include "y2020/control_loops/superstructure/superstructure_status_generated.h"
@@ -40,6 +41,8 @@
CHECK(aos::configuration::MultiNode(reader.configuration()));
// Find the nodes we care about.
+ const aos::Node *const imu_node =
+ aos::configuration::GetNode(factory.configuration(), "imu");
const aos::Node *const roborio_node =
aos::configuration::GetNode(factory.configuration(), "roborio");
@@ -49,17 +52,20 @@
const aos::Node *const pi_node = aos::configuration::GetNode(
factory.configuration(), absl::StrCat("pi", *pi_number));
+ LOG(INFO) << "imu " << aos::FlatbufferToJson(imu_node);
LOG(INFO) << "roboRIO " << aos::FlatbufferToJson(roborio_node);
LOG(INFO) << "Pi " << aos::FlatbufferToJson(pi_node);
+ std::unique_ptr<aos::EventLoop> imu_event_loop =
+ factory.MakeEventLoop("calibration", imu_node);
std::unique_ptr<aos::EventLoop> roborio_event_loop =
factory.MakeEventLoop("calibration", roborio_node);
std::unique_ptr<aos::EventLoop> pi_event_loop =
factory.MakeEventLoop("calibration", pi_node);
// Now, hook Calibration up to everything.
- Calibration extractor(&factory, pi_event_loop.get(),
- roborio_event_loop.get(), FLAGS_pi, &data);
+ Calibration extractor(&factory, pi_event_loop.get(), imu_event_loop.get(),
+ FLAGS_pi, &data);
if (FLAGS_turret) {
aos::NodeEventLoopFactory *roborio_factory =
@@ -89,25 +95,42 @@
Eigen::Vector3d(0.0, 0.0, M_PI)));
const Eigen::Quaternion<double> nominal_pivot_to_camera(
Eigen::AngleAxisd(-0.5 * M_PI, Eigen::Vector3d::UnitX()));
+ const Eigen::Quaternion<double> nominal_pivot_to_imu(
+ Eigen::AngleAxisd(0.0, Eigen::Vector3d::UnitX()));
const Eigen::Quaternion<double> nominal_board_to_world(
Eigen::AngleAxisd(0.5 * M_PI, Eigen::Vector3d::UnitX()));
+ Eigen::Matrix<double, 6, 1> nominal_initial_state =
+ Eigen::Matrix<double, 6, 1>::Zero();
+ // Set y value to -1 m (approx distance from imu to board/world
+ nominal_initial_state(1, 0) = -1.0;
CalibrationParameters calibration_parameters;
calibration_parameters.initial_orientation = nominal_initial_orientation;
calibration_parameters.pivot_to_camera = nominal_pivot_to_camera;
+ calibration_parameters.pivot_to_imu = nominal_pivot_to_imu;
calibration_parameters.board_to_world = nominal_board_to_world;
+ calibration_parameters.initial_state = nominal_initial_state;
+ if (data.turret_samples_size() > 0) {
+ LOG(INFO) << "Have turret, so using pivot setup";
+ calibration_parameters.has_pivot = true;
+ }
Solve(data, &calibration_parameters);
LOG(INFO) << "Nominal initial_orientation "
<< nominal_initial_orientation.coeffs().transpose();
LOG(INFO) << "Nominal pivot_to_camera "
<< nominal_pivot_to_camera.coeffs().transpose();
-
- LOG(INFO) << "pivot_to_camera delta "
+ LOG(INFO) << "Nominal pivot_to_camera (rot-xyz) "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ nominal_pivot_to_camera)
+ .transpose();
+ LOG(INFO) << "pivot_to_camera change "
<< frc971::controls::ToRotationVectorFromQuaternion(
calibration_parameters.pivot_to_camera *
nominal_pivot_to_camera.inverse())
.transpose();
+ LOG(INFO) << "Nominal pivot_to_imu "
+ << nominal_pivot_to_imu.coeffs().transpose();
LOG(INFO) << "board_to_world delta "
<< frc971::controls::ToRotationVectorFromQuaternion(
calibration_parameters.board_to_world *
diff --git a/y2022/BUILD b/y2022/BUILD
index 5d29f04..4a351a5 100644
--- a/y2022/BUILD
+++ b/y2022/BUILD
@@ -47,6 +47,7 @@
],
data = [
":aos_config",
+ ":message_bridge_client.sh",
"//y2022/image_streamer:image_streamer_start",
],
dirs = [
diff --git a/y2022/constants.cc b/y2022/constants.cc
index c97058e..444e87d 100644
--- a/y2022/constants.cc
+++ b/y2022/constants.cc
@@ -131,23 +131,23 @@
// Interpolation table for comp and practice robots
r.shot_interpolation_table = InterpolationTable<Values::ShotParams>({
- {1.0, {0.05, 19.4}},
- {1.6, {0.05, 19.4}},
- {1.9, {0.1, 19.4}},
- {2.12, {0.13, 19.4}},
- {2.9, {0.24, 19.9}},
+ {1.0, {0.12, 19.4}},
+ {1.6, {0.12, 19.4}},
+ {1.9, {0.17, 19.4}},
+ {2.12, {0.21, 19.4}},
+ {2.9, {0.30, 19.9}},
- {3.2, {0.26, 20.7}},
+ {3.2, {0.33, 20.1}},
- {3.60, {0.33, 20.9}},
- {4.50, {0.38, 22.5}},
- {4.9, {0.4, 22.9}},
- {5.4, {0.4, 23.9}},
+ {3.60, {0.39, 20.65}},
+ {4.50, {0.44, 22.3}},
+ {4.9, {0.43, 22.75}}, // up to here
+ {5.4, {0.43, 23.85}},
- {6.0, {0.40, 25.4}},
- {7.0, {0.37, 28.1}},
+ {6.0, {0.42, 25.3}},
+ {7.0, {0.40, 27.7}},
- {10.0, {0.37, 28.1}},
+ {10.0, {0.40, 27.7}},
});
if (false) {
@@ -238,13 +238,22 @@
0.0634440443622909 + 0.213601224728352 + 0.0657973101027296 -
0.114726411377978 - 0.980314029089968 - 0.0266013159299456 +
0.0631240002215899 + 0.222882504808653 + 0.0370686419434252 -
- 0.0965027214840068 - 0.126737479717192;
+ 0.0965027214840068 - 0.126737479717192 - 0.0773753775457 +
+ 2.8132444751306;
turret->subsystem_params.zeroing_constants.measured_absolute_position =
- 1.3081068967929;
+ 1.16683731504739;
flipper_arm_left->potentiometer_offset = -6.4;
flipper_arm_right->potentiometer_offset = 5.56;
+ *turret_range = ::frc971::constants::Range{
+ .lower_hard = -7.0, // Back Hard
+ .upper_hard = 3.4, // Front Hard
+ .lower = -6.4, // Back Soft
+ .upper = 2.9 // Front Soft
+ };
+ turret_params->range = *turret_range;
+
catapult_params->zeroing_constants.measured_absolute_position =
1.71723370408082;
catapult->potentiometer_offset = -2.03383240293769;
@@ -256,6 +265,8 @@
break;
case kPracticeTeamNumber:
+ catapult_params->range.lower = -0.885;
+
r.shot_interpolation_table = InterpolationTable<Values::ShotParams>({
{1.0, {0.08, 20.0}},
{1.6, {0.08, 20.0}},
@@ -276,7 +287,8 @@
{10.0, {0.39, 28.25}},
});
- climber->potentiometer_offset = -0.1209073362519 + 0.0760598;
+ climber->potentiometer_offset =
+ -0.1209073362519 + 0.0760598 - 0.0221716219244 - 0.00321684;
intake_front->potentiometer_offset = 3.06604378582351 - 0.60745632979918;
intake_front->subsystem_params.zeroing_constants
.measured_absolute_position = 0.143667561169188;
@@ -291,14 +303,18 @@
0.0718028442723373 - 0.0793332946417493 + 0.233707527214682 +
0.0828349540635251 + 0.677740533247017 - 0.0828349540635251 -
0.0903654044329345 - 0.105426305171759 - 0.150609007388226 -
- 0.0338870266623506 - 0.0677740533247011;
+ 0.0338870266623506 - 0.0677740533247011 - 0.135548106649404 - 0.6852;
turret->subsystem_params.zeroing_constants.measured_absolute_position =
- 1.50798193457968;
- turret_range->upper = 2.9;
- turret_range->lower = -6.4;
+ 0.8306;
+ *turret_range = ::frc971::constants::Range{
+ .lower_hard = -7.0, // Back Hard
+ .upper_hard = 3.4, // Front Hard
+ .lower = -6.4, // Back Soft
+ .upper = 2.9 // Front Soft
+ };
turret_params->range = *turret_range;
- flipper_arm_left->potentiometer_offset = -4.39536583413615;
- flipper_arm_right->potentiometer_offset = 4.36264091401229;
+ flipper_arm_left->potentiometer_offset = -4.39536583413615 - 0.108401297910291;
+ flipper_arm_right->potentiometer_offset = 4.36264091401229 + 0.175896445665755;
catapult_params->zeroing_constants.measured_absolute_position =
1.62909518684227;
diff --git a/y2022/message_bridge_client.sh b/y2022/message_bridge_client.sh
index c81076a..733905e 100755
--- a/y2022/message_bridge_client.sh
+++ b/y2022/message_bridge_client.sh
@@ -5,7 +5,37 @@
ping -c 1 pi1 -W 1 && break;
sleep 1
done
+while true;
+do
+ ping -c 1 pi2 -W 1 && break;
+ sleep 1
+done
+while true;
+do
+ ping -c 1 pi3 -W 1 && break;
+ sleep 1
+done
+while true;
+do
+ ping -c 1 pi4 -W 1 && break;
+ sleep 1
+done
+while true;
+do
+ ping -c 1 pi5 -W 1 && break;
+ sleep 1
+done
+while true;
+do
+ ping -c 1 pi6 -W 1 && break;
+ sleep 1
+done
+while true;
+do
+ ping -c 1 roborio -W 1 && break;
+ sleep 1
+done
echo Pinged
-exec /home/admin/bin/message_bridge_client "$@"
+exec message_bridge_client "$@"
diff --git a/y2022/vision/BUILD b/y2022/vision/BUILD
index 4fda1ad..9726542 100644
--- a/y2022/vision/BUILD
+++ b/y2022/vision/BUILD
@@ -328,9 +328,9 @@
)
cc_binary(
- name = "extrinsics_calibration",
+ name = "calibrate_extrinsics",
srcs = [
- "extrinsics_calibration.cc",
+ "calibrate_extrinsics.cc",
],
target_compatible_with = ["@platforms//os:linux"],
visibility = ["//y2022:__subpackages__"],
@@ -339,6 +339,7 @@
"//aos/events/logging:log_reader",
"//frc971/control_loops:profiled_subsystem_fbs",
"//frc971/vision:extrinsics_calibration",
+ "//third_party:opencv",
"//y2022/control_loops/superstructure:superstructure_status_fbs",
],
)
diff --git a/y2022/vision/calibrate_extrinsics.cc b/y2022/vision/calibrate_extrinsics.cc
new file mode 100644
index 0000000..521992c
--- /dev/null
+++ b/y2022/vision/calibrate_extrinsics.cc
@@ -0,0 +1,236 @@
+#include "Eigen/Dense"
+#include "Eigen/Geometry"
+#include "absl/strings/str_format.h"
+#include "aos/events/logging/log_reader.h"
+#include "aos/init.h"
+#include "aos/network/team_number.h"
+#include "aos/time/time.h"
+#include "aos/util/file.h"
+#include "frc971/control_loops/quaternion_utils.h"
+#include "frc971/vision/extrinsics_calibration.h"
+#include "frc971/vision/vision_generated.h"
+#include "frc971/wpilib/imu_batch_generated.h"
+#include "y2020/vision/sift/sift_generated.h"
+#include "y2020/vision/sift/sift_training_generated.h"
+#include "y2020/vision/tools/python_code/sift_training_data.h"
+#include "y2022/control_loops/superstructure/superstructure_status_generated.h"
+
+DEFINE_string(pi, "pi-7971-2", "Pi name to calibrate.");
+DEFINE_bool(plot, false, "Whether to plot the resulting data.");
+DEFINE_bool(turret, true, "If true, the camera is on the turret");
+
+namespace frc971 {
+namespace vision {
+namespace chrono = std::chrono;
+using aos::distributed_clock;
+using aos::monotonic_clock;
+
+// TODO(austin): Source of IMU data? Is it the same?
+// TODO(austin): Intrinsics data?
+
+void Main(int argc, char **argv) {
+ CalibrationData data;
+
+ {
+ // Now, accumulate all the data into the data object.
+ aos::logger::LogReader reader(
+ aos::logger::SortParts(aos::logger::FindLogs(argc, argv)));
+
+ aos::SimulatedEventLoopFactory factory(reader.configuration());
+ reader.Register(&factory);
+
+ CHECK(aos::configuration::MultiNode(reader.configuration()));
+
+ // Find the nodes we care about.
+ const aos::Node *const imu_node =
+ aos::configuration::GetNode(factory.configuration(), "imu");
+ const aos::Node *const roborio_node =
+ aos::configuration::GetNode(factory.configuration(), "roborio");
+
+ std::optional<uint16_t> pi_number = aos::network::ParsePiNumber(FLAGS_pi);
+ CHECK(pi_number);
+ LOG(INFO) << "Pi " << *pi_number;
+ const aos::Node *const pi_node = aos::configuration::GetNode(
+ factory.configuration(), absl::StrCat("pi", *pi_number));
+
+ LOG(INFO) << "imu " << aos::FlatbufferToJson(imu_node);
+ LOG(INFO) << "roboRIO " << aos::FlatbufferToJson(roborio_node);
+ LOG(INFO) << "Pi " << aos::FlatbufferToJson(pi_node);
+
+ std::unique_ptr<aos::EventLoop> imu_event_loop =
+ factory.MakeEventLoop("calibration", imu_node);
+ std::unique_ptr<aos::EventLoop> roborio_event_loop =
+ factory.MakeEventLoop("calibration", roborio_node);
+ std::unique_ptr<aos::EventLoop> pi_event_loop =
+ factory.MakeEventLoop("calibration", pi_node);
+
+ // Now, hook Calibration up to everything.
+ Calibration extractor(&factory, pi_event_loop.get(), imu_event_loop.get(),
+ FLAGS_pi, &data);
+
+ if (FLAGS_turret) {
+ aos::NodeEventLoopFactory *roborio_factory =
+ factory.GetNodeEventLoopFactory(roborio_node->name()->string_view());
+ roborio_event_loop->MakeWatcher(
+ "/superstructure",
+ [roborio_factory, roborio_event_loop = roborio_event_loop.get(),
+ &data](const y2022::control_loops::superstructure::Status &status) {
+ data.AddTurret(
+ roborio_factory->ToDistributedClock(
+ roborio_event_loop->context().monotonic_event_time),
+ Eigen::Vector2d(status.turret()->position(),
+ status.turret()->velocity()));
+ });
+ }
+
+ factory.Run();
+
+ reader.Deregister();
+ }
+
+ LOG(INFO) << "Done with event_loop running";
+ CHECK(data.imu_samples_size() > 0) << "Didn't get any IMU data";
+ CHECK(data.camera_samples_size() > 0) << "Didn't get any camera observations";
+
+ // And now we have it, we can start processing it.
+ const Eigen::Quaternion<double> nominal_initial_orientation(
+ frc971::controls::ToQuaternionFromRotationVector(
+ Eigen::Vector3d(0.0, 0.0, M_PI)));
+ const Eigen::Quaternion<double> nominal_pivot_to_camera(
+ Eigen::AngleAxisd(-0.5 * M_PI, Eigen::Vector3d::UnitX()));
+ const Eigen::Quaternion<double> nominal_pivot_to_imu(
+ Eigen::AngleAxisd(0.0, Eigen::Vector3d::UnitX()));
+ const Eigen::Quaternion<double> nominal_board_to_world(
+ Eigen::AngleAxisd(0.5 * M_PI, Eigen::Vector3d::UnitX()));
+ Eigen::Matrix<double, 6, 1> nominal_initial_state =
+ Eigen::Matrix<double, 6, 1>::Zero();
+ // Set x value to 0.5 m (center view on the board)
+ // nominal_initial_state(0, 0) = 0.5;
+ // Set y value to -1 m (approx distance from imu to board/world)
+ nominal_initial_state(1, 0) = -1.0;
+
+ CalibrationParameters calibration_parameters;
+ calibration_parameters.initial_orientation = nominal_initial_orientation;
+ calibration_parameters.pivot_to_camera = nominal_pivot_to_camera;
+ calibration_parameters.pivot_to_imu = nominal_pivot_to_imu;
+ calibration_parameters.board_to_world = nominal_board_to_world;
+ calibration_parameters.initial_state = nominal_initial_state;
+
+ // Show the inverse of pivot_to_camera, since camera_to_pivot tells where the
+ // camera is with respect to the pivot frame
+ const Eigen::Affine3d nominal_affine_pivot_to_camera =
+ Eigen::Translation3d(calibration_parameters.pivot_to_camera_translation) *
+ nominal_pivot_to_camera;
+ const Eigen::Quaterniond nominal_camera_to_pivot_rotation(
+ nominal_affine_pivot_to_camera.inverse().rotation());
+ const Eigen::Vector3d nominal_camera_to_pivot_translation(
+ nominal_affine_pivot_to_camera.inverse().translation());
+
+ if (data.turret_samples_size() > 0) {
+ LOG(INFO) << "Have turret, so using pivot setup";
+ calibration_parameters.has_pivot = true;
+ }
+
+ LOG(INFO) << "Initial Conditions for solver. Assumes:\n"
+ << "1) board origin is same as world, but rotated pi/2 about "
+ "x-axis, so z points out\n"
+ << "2) pivot origin matches imu origin\n"
+ << "3) camera is offset from pivot (depends on which camera)";
+
+ LOG(INFO)
+ << "Nominal initial_orientation of imu w.r.t. world (angle-axis vector): "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ nominal_initial_orientation)
+ .transpose();
+ LOG(INFO) << "Nominal initial_state: \n"
+ << "Position: "
+ << nominal_initial_state.block<3, 1>(0, 0).transpose() << "\n"
+ << "Velocity: "
+ << nominal_initial_state.block<3, 1>(3, 0).transpose();
+ LOG(INFO) << "Nominal pivot_to_imu (angle-axis vector) "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ calibration_parameters.pivot_to_imu)
+ .transpose();
+ LOG(INFO) << "Nominal pivot_to_imu translation: "
+ << calibration_parameters.pivot_to_imu_translation.transpose();
+ // TODO<Jim>: Might be nice to take out the rotation component that maps into
+ // camera image coordinates (with x right, y down, z forward)
+ LOG(INFO) << "Nominal camera_to_pivot (angle-axis vector): "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ nominal_camera_to_pivot_rotation)
+ .transpose();
+ LOG(INFO) << "Nominal camera_to_pivot translation: "
+ << nominal_camera_to_pivot_translation.transpose();
+
+ Solve(data, &calibration_parameters);
+
+ LOG(INFO) << "RESULTS OF CALIBRATION SOLVER:";
+ LOG(INFO) << "initial_orientation of imu w.r.t. world (angle-axis vector): "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ calibration_parameters.initial_orientation)
+ .transpose();
+ LOG(INFO)
+ << "initial_state: \n"
+ << "Position: "
+ << calibration_parameters.initial_state.block<3, 1>(0, 0).transpose()
+ << "\n"
+ << "Velocity: "
+ << calibration_parameters.initial_state.block<3, 1>(3, 0).transpose();
+
+ LOG(INFO) << "pivot_to_imu rotation (angle-axis vec) "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ calibration_parameters.pivot_to_imu)
+ .transpose();
+ LOG(INFO) << "pivot_to_imu_translation "
+ << calibration_parameters.pivot_to_imu_translation.transpose();
+ const Eigen::Affine3d affine_pivot_to_camera =
+ Eigen::Translation3d(calibration_parameters.pivot_to_camera_translation) *
+ calibration_parameters.pivot_to_camera;
+ const Eigen::Quaterniond camera_to_pivot_rotation(
+ affine_pivot_to_camera.inverse().rotation());
+ const Eigen::Vector3d camera_to_pivot_translation(
+ affine_pivot_to_camera.inverse().translation());
+ LOG(INFO) << "camera to pivot (angle-axis vec): "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ camera_to_pivot_rotation)
+ .transpose();
+ LOG(INFO) << "camera to pivot translation: "
+ << camera_to_pivot_translation.transpose();
+ LOG(INFO) << "board_to_world (rotation) "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ calibration_parameters.board_to_world)
+ .transpose();
+ LOG(INFO) << "accelerometer bias "
+ << calibration_parameters.accelerometer_bias.transpose();
+ LOG(INFO) << "gyro_bias " << calibration_parameters.gyro_bias.transpose();
+ LOG(INFO) << "gravity " << 9.81 * calibration_parameters.gravity_scalar;
+
+ LOG(INFO) << "pivot_to_camera change "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ calibration_parameters.pivot_to_camera *
+ nominal_pivot_to_camera.inverse())
+ .transpose();
+ LOG(INFO) << "board_to_world delta "
+ << frc971::controls::ToRotationVectorFromQuaternion(
+ calibration_parameters.board_to_world *
+ nominal_board_to_world.inverse())
+ .transpose();
+
+ if (FLAGS_visualize) {
+ LOG(INFO) << "Showing visualization";
+ Visualize(data, calibration_parameters);
+ }
+
+ if (FLAGS_plot) {
+ Plot(data, calibration_parameters);
+ }
+}
+
+} // namespace vision
+} // namespace frc971
+
+int main(int argc, char **argv) {
+ aos::InitGoogle(&argc, &argv);
+
+ frc971::vision::Main(argc, argv);
+}
diff --git a/y2022/vision/camera_definition.py b/y2022/vision/camera_definition.py
index d3e44b7..3b34ca2 100644
--- a/y2022/vision/camera_definition.py
+++ b/y2022/vision/camera_definition.py
@@ -109,24 +109,24 @@
camera_yaw = 0.0
T = np.array([-9.5 * 0.0254, -3.5 * 0.0254, 34.5 * 0.0254])
elif pi_number == "pi3":
- camera_yaw = 179.0 * np.pi / 180.0
+ camera_yaw = 182.0 * np.pi / 180.0
T = np.array([-9.5 * 0.0254, 3.5 * 0.0254, 34.5 * 0.0254])
elif pi_number == "pi4":
camera_yaw = -90.0 * np.pi / 180.0
T = np.array([-10.25 * 0.0254, -5.0 * 0.0254, 27.5 * 0.0254])
elif team_number == 9971:
if pi_number == "pi1":
- camera_yaw = 180.5 * np.pi / 180.0
+ camera_yaw = 179.0 * np.pi / 180.0
T = np.array([-9.5 * 0.0254, 3.25 * 0.0254, 35.5 * 0.0254])
elif pi_number == "pi2":
camera_yaw = 0.0
T = np.array([-9.0 * 0.0254, -3.25 * 0.0254, 35.5 * 0.0254])
elif pi_number == "pi3":
camera_yaw = 90.0 * np.pi / 180.0
- T = np.array([-10.5 * 0.0254, -5.0 * 0.0254, 29.5 * 0.0254])
+ T = np.array([-10.5 * 0.0254, 5.0 * 0.0254, 29.5 * 0.0254])
elif pi_number == "pi4":
camera_yaw = -90.0 * np.pi / 180.0
- T = np.array([-10.5 * 0.0254, 5.0 * 0.0254, 28.0 * 0.0254])
+ T = np.array([-10.5 * 0.0254, -5.0 * 0.0254, 28.5 * 0.0254])
else:
glog.fatal("Unknown team number for extrinsics")
diff --git a/y2022/vision/extrinsics_calibration.cc b/y2022/vision/extrinsics_calibration.cc
deleted file mode 100644
index 49f2ca3..0000000
--- a/y2022/vision/extrinsics_calibration.cc
+++ /dev/null
@@ -1,129 +0,0 @@
-#include "frc971/vision/extrinsics_calibration.h"
-
-#include "Eigen/Dense"
-#include "Eigen/Geometry"
-#include "absl/strings/str_format.h"
-#include "aos/events/logging/log_reader.h"
-#include "aos/init.h"
-#include "aos/network/team_number.h"
-#include "aos/time/time.h"
-#include "aos/util/file.h"
-#include "frc971/control_loops/quaternion_utils.h"
-#include "frc971/vision/vision_generated.h"
-#include "frc971/wpilib/imu_batch_generated.h"
-#include "y2022/control_loops/superstructure/superstructure_status_generated.h"
-#include "y2020/vision/sift/sift_generated.h"
-#include "y2020/vision/sift/sift_training_generated.h"
-#include "y2020/vision/tools/python_code/sift_training_data.h"
-
-DEFINE_string(pi, "pi-7971-2", "Pi name to calibrate.");
-DEFINE_bool(plot, false, "Whether to plot the resulting data.");
-
-namespace frc971 {
-namespace vision {
-namespace chrono = std::chrono;
-using aos::distributed_clock;
-using aos::monotonic_clock;
-
-// TODO(austin): Source of IMU data? Is it the same?
-// TODO(austin): Intrinsics data?
-
-void Main(int argc, char **argv) {
- CalibrationData data;
-
- {
- // Now, accumulate all the data into the data object.
- aos::logger::LogReader reader(
- aos::logger::SortParts(aos::logger::FindLogs(argc, argv)));
-
- aos::SimulatedEventLoopFactory factory(reader.configuration());
- reader.Register(&factory);
-
- CHECK(aos::configuration::MultiNode(reader.configuration()));
-
- // Find the nodes we care about.
- const aos::Node *const roborio_node =
- aos::configuration::GetNode(factory.configuration(), "roborio");
-
- std::optional<uint16_t> pi_number = aos::network::ParsePiNumber(FLAGS_pi);
- CHECK(pi_number);
- LOG(INFO) << "Pi " << *pi_number;
- const aos::Node *const pi_node = aos::configuration::GetNode(
- factory.configuration(), absl::StrCat("pi", *pi_number));
-
- LOG(INFO) << "roboRIO " << aos::FlatbufferToJson(roborio_node);
- LOG(INFO) << "Pi " << aos::FlatbufferToJson(pi_node);
-
- std::unique_ptr<aos::EventLoop> roborio_event_loop =
- factory.MakeEventLoop("calibration", roborio_node);
- std::unique_ptr<aos::EventLoop> pi_event_loop =
- factory.MakeEventLoop("calibration", pi_node);
-
- // Now, hook Calibration up to everything.
- Calibration extractor(&factory, pi_event_loop.get(),
- roborio_event_loop.get(), FLAGS_pi, &data);
-
- aos::NodeEventLoopFactory *roborio_factory =
- factory.GetNodeEventLoopFactory(roborio_node->name()->string_view());
- roborio_event_loop->MakeWatcher(
- "/superstructure",
- [roborio_factory, roborio_event_loop = roborio_event_loop.get(),
- &data](const y2022::control_loops::superstructure::Status &status) {
- data.AddTurret(
- roborio_factory->ToDistributedClock(
- roborio_event_loop->context().monotonic_event_time),
- Eigen::Vector2d(status.turret()->position(),
- status.turret()->velocity()));
- });
-
- factory.Run();
-
- reader.Deregister();
- }
-
- LOG(INFO) << "Done with event_loop running";
- // And now we have it, we can start processing it.
-
- const Eigen::Quaternion<double> nominal_initial_orientation(
- frc971::controls::ToQuaternionFromRotationVector(
- Eigen::Vector3d(0.0, 0.0, M_PI)));
- const Eigen::Quaternion<double> nominal_pivot_to_camera(
- Eigen::AngleAxisd(-0.5 * M_PI, Eigen::Vector3d::UnitX()));
- const Eigen::Quaternion<double> nominal_board_to_world(
- Eigen::AngleAxisd(0.5 * M_PI, Eigen::Vector3d::UnitX()));
-
- CalibrationParameters calibration_parameters;
- calibration_parameters.initial_orientation = nominal_initial_orientation;
- calibration_parameters.pivot_to_camera = nominal_pivot_to_camera;
- calibration_parameters.board_to_world = nominal_board_to_world;
-
- Solve(data, &calibration_parameters);
- LOG(INFO) << "Nominal initial_orientation "
- << nominal_initial_orientation.coeffs().transpose();
- LOG(INFO) << "Nominal pivot_to_camera "
- << nominal_pivot_to_camera.coeffs().transpose();
-
- LOG(INFO) << "pivot_to_camera delta "
- << frc971::controls::ToRotationVectorFromQuaternion(
- calibration_parameters.pivot_to_camera *
- nominal_pivot_to_camera.inverse())
- .transpose();
- LOG(INFO) << "board_to_world delta "
- << frc971::controls::ToRotationVectorFromQuaternion(
- calibration_parameters.board_to_world *
- nominal_board_to_world.inverse())
- .transpose();
-
- if (FLAGS_plot) {
- Plot(data, calibration_parameters);
- }
-}
-
-} // namespace vision
-} // namespace frc971
-
-int main(int argc, char **argv) {
- aos::InitGoogle(&argc, &argv);
-
- frc971::vision::Main(argc, argv);
-}
diff --git a/y2022/y2022_imu.json b/y2022/y2022_imu.json
index 817f051..bd2b326 100644
--- a/y2022/y2022_imu.json
+++ b/y2022/y2022_imu.json
@@ -367,7 +367,7 @@
"applications": [
{
"name": "message_bridge_client",
- "executable_name": "message_bridge_client",
+ "executable_name": "message_bridge_client.sh",
"nodes": [
"imu"
]
diff --git a/y2022/y2022_logger.json b/y2022/y2022_logger.json
index a8a4bbd..0f790c7 100644
--- a/y2022/y2022_logger.json
+++ b/y2022/y2022_logger.json
@@ -462,6 +462,38 @@
]
},
{
+ "name": "/pi3/camera",
+ "type": "frc971.vision.CameraImage",
+ "source_node": "pi3",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": [
+ "logger"
+ ],
+ "destination_nodes": [
+ {
+ "name": "logger",
+ "priority": 3,
+ "time_to_live": 500000000
+ }
+ ]
+ },
+ {
+ "name": "/localizer",
+ "type": "frc971.IMUValuesBatch",
+ "source_node": "imu",
+ "logger": "LOCAL_AND_REMOTE_LOGGER",
+ "logger_nodes": [
+ "logger"
+ ],
+ "destination_nodes": [
+ {
+ "name": "logger",
+ "priority": 3,
+ "time_to_live": 500000000
+ }
+ ]
+ },
+ {
"name": "/pi4/camera/decimated",
"type": "frc971.vision.CameraImage",
"source_node": "pi4",
@@ -502,7 +534,7 @@
"applications": [
{
"name": "logger_message_bridge_client",
- "executable_name": "message_bridge_client",
+ "executable_name": "message_bridge_client.sh",
"args": ["--rmem=8388608", "--rt_priority=16"],
"nodes": [
"logger"
diff --git a/y2022/y2022_pi_template.json b/y2022/y2022_pi_template.json
index a6b3f4a..99b04a1 100644
--- a/y2022/y2022_pi_template.json
+++ b/y2022/y2022_pi_template.json
@@ -186,7 +186,7 @@
"name": "/pi{{ NUM }}/camera",
"type": "y2022.vision.TargetEstimate",
"source_node": "pi{{ NUM }}",
- "frequency": 40,
+ "frequency": 80,
"num_senders": 2,
"max_size": 40000,
"logger": "LOCAL_AND_REMOTE_LOGGER",
@@ -358,7 +358,7 @@
"applications": [
{
"name": "message_bridge_client",
- "executable_name": "message_bridge_client",
+ "executable_name": "message_bridge_client.sh",
"args": ["--rt_priority=16"],
"nodes": [
"pi{{ NUM }}"