Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[glass] Add camera stream viewer #6637

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion glass/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,27 @@ install(TARGETS libglassnt EXPORT libglassnt)
export(TARGETS libglassnt FILE libglassnt.cmake NAMESPACE libglassnt::)
install(DIRECTORY src/libnt/native/include/ DESTINATION "${include_dest}/glass")

#
# libglasscs
#
file(GLOB_RECURSE libglasscs_src src/libcs/native/cpp/*.cpp)

add_library(libglasscs STATIC ${libglasscs_src})
set_target_properties(libglasscs PROPERTIES DEBUG_POSTFIX "d" OUTPUT_NAME "glasscs")
set_property(TARGET libglasscs PROPERTY POSITION_INDEPENDENT_CODE ON)

set_property(TARGET libglasscs PROPERTY FOLDER "libraries")

wpilib_target_warnings(libglasscs)
target_link_libraries(libglasscs PUBLIC cscore libglassnt)

target_include_directories(libglasscs PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/libcs/native/include>
$<INSTALL_INTERFACE:${include_dest}/glass>)

install(TARGETS libglasscs EXPORT libglasscs DESTINATION "${main_lib_dest}")
install(DIRECTORY src/libcs/native/include/ DESTINATION "${include_dest}/glass")

#
# glass application
#
Expand All @@ -79,7 +100,7 @@ endif()
add_executable(glass ${glass_src} ${glass_resources_src} ${glass_rc} ${APP_ICON_MACOSX})
wpilib_link_macos_gui(glass)
wpilib_target_warnings(glass)
target_link_libraries(glass libglassnt libglass)
target_link_libraries(glass libglasscs libglassnt libglass)

if(WIN32)
set_target_properties(glass PROPERTIES WIN32_EXECUTABLE YES)
Expand Down
35 changes: 34 additions & 1 deletion glass/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,38 @@ model {
}
appendDebugPathToBinaries(binaries)
}
"${nativeName}cs"(NativeLibrarySpec) {
sources.cpp {
source {
srcDirs = ['src/libcs/native/cpp']
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/libcs/native/include'
}
}
binaries.all {
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio) {
it.buildable = false
return
}
if (it instanceof SharedLibraryBinarySpec) {
it.buildable = false
return
}
lib library: nativeName, linkage: 'static'
project(':ntcore').addNtcoreDependency(it, 'shared')
lib project: ':cscore', library: 'cscore', linkage: 'shared'
nativeUtils.useRequiredLibrary(it, 'opencv_shared')
lib project: ':wpinet', library: 'wpinet', linkage: 'shared'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'shared'
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
lib project: ':fieldImages', library: 'fieldImages', linkage: 'shared'
nativeUtils.useRequiredLibrary(it, 'imgui')
}
appendDebugPathToBinaries(binaries)
}
"${nativeName}nt"(NativeLibrarySpec) {
sources.cpp {
source {
Expand Down Expand Up @@ -157,9 +189,10 @@ model {
it.buildable = false
return
}
lib project: ':cscore', library: 'cscore', linkage: 'static'
lib library: 'glasscs', linkage: 'static'
lib library: 'glassnt', linkage: 'static'
lib library: nativeName, linkage: 'static'
lib project: ':cscore', library: 'cscore', linkage: 'static'
project(':ntcore').addNtcoreDependency(it, 'static')
lib project: ':wpinet', library: 'wpinet', linkage: 'static'
lib project: ':wpiutil', library: 'wpiutil', linkage: 'static'
Expand Down
37 changes: 37 additions & 0 deletions glass/publish.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ def libntBaseArtifactId = 'libglassnt'
def libntArtifactGroupId = 'edu.wpi.first.glass'
def libntZipBaseName = '_GROUP_edu_wpi_first_glass_ID_libglassnt_CLS'

def libcsBaseArtifactId = 'libglasscs'
def libcsArtifactGroupId = 'edu.wpi.first.glass'
def libcsZipBaseName = '_GROUP_edu_wpi_first_glass_ID_libglasscs_CLS'

def outputsFolder = file("$project.buildDir/outputs")

task libCppSourcesZip(type: Zip) {
Expand Down Expand Up @@ -50,15 +54,37 @@ task libntCppHeadersZip(type: Zip) {
from('src/libnt/native/include') { into '/' }
}

task libcsCppSourcesZip(type: Zip) {
destinationDirectory = outputsFolder
archiveBaseName = libcsZipBaseName
classifier = "sources"

from(licenseFile) { into '/' }
from('src/libcs/native/cpp') { into '/' }
}

task libcsCppHeadersZip(type: Zip) {
destinationDirectory = outputsFolder
archiveBaseName = libcsZipBaseName
classifier = "headers"

from(licenseFile) { into '/' }
from('src/libcs/native/include') { into '/' }
}

build.dependsOn libCppHeadersZip
build.dependsOn libCppSourcesZip
build.dependsOn libntCppHeadersZip
build.dependsOn libntCppSourcesZip
build.dependsOn libcsCppHeadersZip
build.dependsOn libcsCppSourcesZip

addTaskToCopyAllOutputs(libCppHeadersZip)
addTaskToCopyAllOutputs(libCppSourcesZip)
addTaskToCopyAllOutputs(libntCppHeadersZip)
addTaskToCopyAllOutputs(libntCppSourcesZip)
addTaskToCopyAllOutputs(libcsCppHeadersZip)
addTaskToCopyAllOutputs(libcsCppSourcesZip)

model {
tasks {
Expand Down Expand Up @@ -168,6 +194,7 @@ model {

def libGlassTaskList = createComponentZipTasks($.components, ['glass'], libZipBaseName, Zip, project, includeStandardZipFormat)
def libGlassntTaskList = createComponentZipTasks($.components, ['glassnt'], libntZipBaseName, Zip, project, includeStandardZipFormat)
def libGlasscsTaskList = createComponentZipTasks($.components, ['glasscs'], libcsZipBaseName, Zip, project, includeStandardZipFormat)

publications {
glassApp(MavenPublication) {
Expand Down Expand Up @@ -197,6 +224,16 @@ model {
groupId = libntArtifactGroupId
version wpilibVersioning.version.get()
}
libglasscs(MavenPublication) {
libGlasscsTaskList.each { artifact it }

artifact libcsCppHeadersZip
artifact libcsCppSourcesZip

artifactId = libcsBaseArtifactId
groupId = libcsArtifactGroupId
version wpilibVersioning.version.get()
}
}
}
}
73 changes: 73 additions & 0 deletions glass/src/app/native/cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <GLFW/glfw3.h>
#include <fmt/format.h>
#include <imgui.h>
#include <cscore_cpp.h>
#include <ntcore_cpp.h>
#include <wpi/StringExtras.h>
#include <wpigui.h>
Expand All @@ -17,6 +18,10 @@
#include "glass/MainMenuBar.h"
#include "glass/Storage.h"
#include "glass/View.h"
#include "glass/Window.h"
#include "glass/camera/Camera.h"
#include "glass/camera/NTCameraProvider.h"
#include "glass/camera/UsbCameraList.h"
#include "glass/networktables/NetworkTables.h"
#include "glass/networktables/NetworkTablesProvider.h"
#include "glass/networktables/NetworkTablesSettings.h"
Expand All @@ -39,6 +44,8 @@ std::string_view GetResource_glass_512_png();

static std::unique_ptr<glass::PlotProvider> gPlotProvider;
static std::unique_ptr<glass::NetworkTablesProvider> gNtProvider;
static std::unique_ptr<glass::NTCameraProvider> gNtCameraProvider;
static std::unique_ptr<glass::UsbCameraList> gUsbCameraList;

static std::unique_ptr<glass::NetworkTablesModel> gNetworkTablesModel;
static std::unique_ptr<glass::NetworkTablesSettings> gNetworkTablesSettings;
Expand All @@ -48,6 +55,8 @@ static std::unique_ptr<glass::Window> gNetworkTablesInfoWindow;
static std::unique_ptr<glass::Window> gNetworkTablesSettingsWindow;
static std::unique_ptr<glass::Window> gNetworkTablesLogWindow;

static glass::Window* gCameraListWindow;

static glass::MainMenuBar gMainMenu;
static bool gAbout = false;
static bool gSetEnterKey = false;
Expand Down Expand Up @@ -192,6 +201,41 @@ static void NtInitialize() {
});
}

static void CsInitialize() {
cs::SetTelemetryPeriod(1.0);
cs::SetLogger([](unsigned int level, const char* file, unsigned int line,
const char* msg) { fmt::print("CS: {}\n", msg); },
CS_LOG_INFO);

gNtCameraProvider = std::make_unique<glass::NTCameraProvider>(
glass::GetStorageRoot().GetChild("NT Cameras"));
gNtCameraProvider->GlobalInit();

gUsbCameraList = std::make_unique<glass::UsbCameraList>();

gCameraListWindow =
glass::imm::CreateWindow("Camera List", false, glass::Window::kHide);
gCameraListWindow->SetDefaultPos(250, 250);
gCameraListWindow->SetDefaultSize(500, 200);
gCameraListWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize);
gui::AddWindowScaler(
[](float scale) { gCameraListWindow->ScaleDefault(scale); });

gui::AddEarlyExecute([] {
glass::Storage& cameras = glass::GetStorageRoot().GetChild("cameras");
for (auto&& storage : cameras.GetChildren()) {
glass::CameraModel* model = glass::GetCameraModel(cameras, storage.key());
if (!model) {
model = glass::GetOrNewCameraModel(cameras, storage.key());
model->Start();
}
model->Update();
}

gUsbCameraList->Update();
});
}

#ifdef _WIN32
int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine,
int nCmdShow) {
Expand Down Expand Up @@ -233,6 +277,7 @@ int main(int argc, char** argv) {
gui::AddInit([] { glass::ResetTime(); });
gNtProvider->GlobalInit();
NtInitialize();
CsInitialize();

glass::AddStandardNetworkTablesViews(*gNtProvider);

Expand Down Expand Up @@ -267,6 +312,27 @@ int main(int argc, char** argv) {
gNtProvider->DisplayMenu();
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Camera")) {
gCameraListWindow->DisplayMenuItem();
if (ImGui::MenuItem("New Camera View")) {
auto& viewsStorage = glass::GetStorageRoot().GetChild("camera views");
auto& views = viewsStorage.GetValues();
// this is an inefficient algorithm, but the number of windows is small
char id[32];
size_t numViews = views.size();
for (size_t i = 0; i <= numViews; ++i) {
wpi::format_to_n_c_str(id, sizeof(id), "Camera View <{}>",
static_cast<int>(i));
if (!views.contains(id)) {
break;
}
}
if (auto win = glass::imm::CreateWindow(viewsStorage, id)) {
win->SetDefaultSize(700, 400);
}
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Plot")) {
bool paused = gPlotProvider->IsPaused();
if (ImGui::MenuItem("Pause All Plots", nullptr, &paused)) {
Expand Down Expand Up @@ -295,6 +361,8 @@ int main(int argc, char** argv) {
});

gui::AddLateExecute([] {
glass::Storage& cameras = glass::GetStorageRoot().GetChild("cameras");

if (gAbout) {
ImGui::OpenPopup("About");
gAbout = false;
Expand Down Expand Up @@ -362,6 +430,11 @@ int main(int argc, char** argv) {
gNetworkTablesSettingsWindow.reset();
gNetworkTablesLogWindow.reset();
gNetworkTablesWindow.reset();

// cs::Shutdown();
gUsbCameraList.reset();
gNtCameraProvider.reset();

gNetworkTablesModel.reset();
gNtProvider.reset();
gPlotProvider.reset();
Expand Down
94 changes: 94 additions & 0 deletions glass/src/libcs/native/cpp/CSCameraProvider.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

#include "glass/camera/CSCameraProvider.h"

#include <algorithm>

#include <cscore_cpp.h>
#include <imgui.h>

using namespace glass;

CSCameraProvider::CSCameraProvider(Storage& storage)
: CameraProviderBase{storage}, m_poller{cs::CreateListenerPoller()} {
CS_Status status = 0;
cs::AddPolledListener(m_poller,
CS_SOURCE_CREATED | CS_SOURCE_DESTROYED |
CS_SOURCE_CONNECTED | CS_SOURCE_DISCONNECTED,
true, &status);
}

CSCameraProvider::~CSCameraProvider() {
cs::DestroyListenerPoller(m_poller);
}

void CSCameraProvider::DisplayMenu() {
bool any = false;
for (auto&& info : m_sourceInfo) {
ImGui::MenuItem(info.name.c_str());
any = true;
}
if (!any) {
ImGui::MenuItem("(None)", nullptr, false, false);
}
}

void CSCameraProvider::Update() {
bool timedOut = false;
for (auto&& event : cs::PollListener(m_poller, 0, &timedOut)) {
if (event.kind == cs::RawEvent::kSourceCreated) {
CS_Status status = 0;
CSSourceInfo info;
info.name = event.name;
info.handle = event.sourceHandle;
info.kind = cs::GetSourceKind(event.sourceHandle, &status);
info.connected = cs::IsSourceConnected(event.sourceHandle, &status);
m_sourceInfo.emplace_back(std::move(info));
continue;
}

auto it = std::find_if(
m_sourceInfo.begin(), m_sourceInfo.end(),
[&](const auto& info) { return info.handle == event.sourceHandle; });
if (it == m_sourceInfo.end()) {
continue;
}
switch (event.kind) {
case cs::RawEvent::kSourceDestroyed:
m_sourceInfo.erase(it);
break;
case cs::RawEvent::kSourceConnected:
it->connected = true;
break;
case cs::RawEvent::kSourceDisconnected:
it->connected = false;
break;
default:
break;
}
}
#if 0
// check for visible windows that need displays (typically this is due to
// file loading)
for (auto&& window : m_windows) {
if (!window->IsVisible() || window->HasView()) {
continue;
}
auto id = window->GetId();

// only handle ones where we have a builder
auto builderIt = m_typeMap.find(typeIt->second.GetName());
if (builderIt == m_typeMap.end()) {
continue;
}

auto entry = GetOrCreateView(
builderIt->second, nt::GetEntry(m_nt.GetInstance(), id + "/.type"), id);
if (entry) {
Show(entry, window.get());
}
}
#endif
}
Loading
Loading