Files
openairinterface5g/openair1/PHY/TOOLS/imscope/imscope.cpp
T
Bartosz Podrygajlo ab75a7ccfb Add IQ file recording and IQ file viewer to ImScope
This commit introduces the following changes to ImScope:
 - Added the ability to record IQ data using the same mechanism as ImScope.
 - Added a new executable target `imscope_iq_file_viewer` for viewing IQ files.
 - Introduced a command line argument `--imscope-record` that enables data recording mode.
 - Refactored a lot of the scope code

Usage:
 - To record IQ data, run a modem with `--imscope-record` flag.
   The recording happens whenever a modem calls dumpScopeData, which currently is
   done only in gNB on PUSCH DTX and PUSCH CRC NOK.
   There is a limit of ~1GB of files written by the thread in a signle run to avoid
   accidentally exhausting disk space.
   If a directory `imscope-dump` is available the data will be recorded there in .imscope
   files. Otherwise files are written to the directory the modem was run. This is done so that
   the directory can be mapped in a docker container.

 - To view recorded IQ files, use the new executable `imscope_iq_file_viewer`. This can be done by running:
      ```
      ./imscope_iq_file_viewer <path_to_iq_file>
      ```
   Replace `<path_to_iq_file>` with the path to the IQ file you want to view.
2025-01-28 16:54:54 +01:00

750 lines
26 KiB
C++

/*
* Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The OpenAirInterface Software Alliance licenses this file to You under
* the OAI Public License, Version 1.1 (the "License"); you may not use this file
* except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.openairinterface.org/?page_id=698
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*-------------------------------------------------------------------------------
* For more information about the OpenAirInterface (OAI) Software Alliance:
* contact@openairinterface.org
*/
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#define GL_SILENCE_DEPRECATION
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <GLES2/gl2.h>
#endif
#include <GLFW/glfw3.h> // Will drag system OpenGL headers
#include "implot.h"
#include "openair1/PHY/defs_nr_UE.h"
extern "C" {
#include "openair1/PHY/TOOLS/phy_scope_interface.h"
#include "executables/softmodem-common.h"
}
#include <iostream>
#include <vector>
#include <limits>
#include <algorithm>
#include <sstream>
#include <mutex>
#include <thread>
#include <fstream>
#include "imscope_internal.h"
#include <cstdlib>
#include <vector>
#define NR_MAX_RB 273
#define N_SC_PER_RB NR_NB_SC_PER_RB
static std::vector<int> rb_boundaries;
void copyDataThreadSafe(void *scopeData,
enum scopeDataType type,
void *dataIn,
int elementSz,
int colSz,
int lineSz,
int offset,
metadata *meta);
bool tryLockScopeData(enum scopeDataType type, int elementSz, int colSz, int lineSz, metadata *meta);
void copyDataUnsafeWithOffset(enum scopeDataType type, void *dataIn, size_t size, size_t offset, int copy_index);
void unlockScopeData(enum scopeDataType type);
static void glfw_error_callback(int error, const char *description)
{
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
}
MovingAverageTimer iq_procedure_timer;
ImScopeDataWrapper scope_array[EXTRA_SCOPE_TYPES];
class LLRPlot {
int len = 0;
float timestamp = 0;
std::vector<int16_t> llr;
bool frozen = false;
bool next = false;
metadata meta;
public:
void Draw(float time, enum scopeDataType type, const char *label)
{
ImGui::BeginGroup();
if (ImGui::Button(frozen ? "Unfreeze" : "Freeze")) {
frozen = !frozen;
next = false;
}
if (frozen) {
ImGui::SameLine();
ImGui::BeginDisabled(next);
if (ImGui::Button("Load next histogram")) {
next = true;
}
ImGui::EndDisabled();
}
ImScopeDataWrapper &scope_data = scope_array[type];
if (ImPlot::BeginPlot(label)) {
if (!frozen || next) {
if (scope_data.is_data_ready) {
iq_procedure_timer.Add(scope_data.data.time_taken_in_ns);
timestamp = time;
const int16_t *tmp = (int16_t *)(scope_data.data.scope_graph_data + 1);
len = scope_data.data.scope_graph_data->lineSz;
llr.reserve(len);
for (auto i = 0; i < len; i++) {
llr[i] = tmp[i];
}
meta = scope_data.data.meta;
scope_data.is_data_ready = false;
if (frozen) {
next = false;
}
}
}
ImPlot::PlotLine(label, llr.data(), len);
ImPlot::EndPlot();
}
std::stringstream ss;
if (meta.slot != -1) {
ss << " slot: " << meta.slot;
}
if (meta.frame != -1) {
ss << " frame: " << meta.frame;
}
if (!ss.str().empty()) {
ImGui::Text("Data for %s", ss.str().c_str());
}
ImGui::Text("Data is %.2f seconds old", time - timestamp);
ImGui::EndGroup();
}
};
class IQHist {
private:
bool frozen = false;
bool next = false;
float range = 100;
int num_bins = 33;
std::string label;
float min_nonzero_percentage = 0.9;
float epsilon = 0.0;
bool auto_adjust_range = true;
int plot_type = 0;
bool disable_scatterplot;
public:
IQHist(const char *label_, bool _disable_scatterplot = false)
{
label = label_;
disable_scatterplot = _disable_scatterplot;
};
bool ShouldReadData(void)
{
return !frozen || next;
}
float GetEpsilon(void)
{
return epsilon;
}
void Draw(IQData *iq_data, float time, bool new_data)
{
if (new_data && frozen && next) {
// Evaluate if new data matches filter settings
if (((float)iq_data->nonzero_count / (float)iq_data->len) > min_nonzero_percentage) {
next = false;
}
}
ImGui::BeginGroup();
ImGui::Checkbox("auto adjust range", &auto_adjust_range);
if (auto_adjust_range) {
if (range < iq_data->max_iq * 1.1) {
range = iq_data->max_iq * 1.1;
}
}
ImGui::BeginDisabled(auto_adjust_range);
ImGui::SameLine();
ImGui::DragFloat("Range", &range, 1, 0.1, std::numeric_limits<int16_t>::max());
ImGui::EndDisabled();
ImGui::DragInt("Number of bins", &num_bins, 1, 33, 101);
if (ImGui::Button(frozen ? "Unfreeze" : "Freeze")) {
frozen = !frozen;
next = false;
}
if (frozen) {
ImGui::SameLine();
ImGui::BeginDisabled(next);
if (ImGui::Button("Load next histogram")) {
next = true;
}
ImGui::EndDisabled();
ImGui::Text("Filter parameters");
ImGui::DragFloat("%% nonzero elements", &min_nonzero_percentage, 1, 0.0, 100);
ImGui::DragFloat("epsilon", &epsilon, 1, 0.0, 3000);
}
const char *items[] = {"Histogram", "RMS", "Scatter"};
ImGui::Combo("Select plot type", &plot_type, items, disable_scatterplot ? 2 : 3);
if (plot_type == 0) {
float x = ImGui::CalcItemWidth();
if (ImPlot::BeginPlot(label.c_str(), {x, x})) {
ImPlot::PlotHistogram2D(label.c_str(),
iq_data->real.data(),
iq_data->imag.data(),
iq_data->len,
num_bins,
num_bins,
ImPlotRect(-range, range, -range, range));
ImPlot::EndPlot();
}
} else if (plot_type == 2) {
float x = ImGui::CalcItemWidth();
if (ImPlot::BeginPlot(label.c_str(), {x, x})) {
int points_drawn = 0;
while (points_drawn < iq_data->len) {
// Limit the amount of data plotted with PlotScatter call (issue with vertices/draw call)
int points_to_draw = std::min(iq_data->len - points_drawn, 16000);
ImPlot::SetNextMarkerStyle(ImPlotMarker_Circle, 1, IMPLOT_AUTO_COL, 1);
ImPlot::PlotScatter(label.c_str(),
iq_data->real.data() + points_drawn,
iq_data->imag.data() + points_drawn,
points_to_draw);
points_drawn += points_to_draw;
}
ImPlot::EndPlot();
}
} else if (plot_type == 1) {
if (ImPlot::BeginPlot(label.c_str())) {
ImPlot::PlotLine(label.c_str(), iq_data->power.data(), iq_data->len);
ImPlot::EndPlot();
}
}
ImGui::Text("Maximum value = %d, nonzero elements/total %d/%d", iq_data->max_iq, iq_data->nonzero_count, iq_data->len);
ImGui::Text("Data is %.2f seconds old", time - iq_data->timestamp);
std::stringstream ss;
if (iq_data->meta.slot != -1) {
ss << " slot: " << iq_data->meta.slot;
}
if (iq_data->meta.frame != -1) {
ss << " frame: " << iq_data->meta.frame;
}
if (!ss.str().empty()) {
ImGui::Text("Data for %s", ss.str().c_str());
}
ImGui::EndGroup();
}
};
class IQSlotHeatmap {
private:
bool frozen = false;
bool next = false;
float timestamp = 0;
std::vector<float> power;
ImScopeDataWrapper *scope_data;
std::string label;
int len = 0;
float max = 0;
float stop_at_min = 1000;
public:
IQSlotHeatmap(ImScopeDataWrapper *scope_data_, const char *label_)
{
scope_data = scope_data_;
label = label_;
};
// Read in the data from the sink and transform it for the use by the scope
void ReadData(float time, int ofdm_symbol_size, int num_symbols, int first_carrier_offset, int num_rb)
{
auto num_sc = num_rb * NR_NB_SC_PER_RB;
if (!frozen || next) {
if (scope_data->is_data_ready) {
iq_procedure_timer.Add(scope_data->data.time_taken_in_ns);
uint16_t first_sc = first_carrier_offset;
uint16_t last_sc = first_sc + num_rb * NR_NB_SC_PER_RB;
bool wrapped = false;
uint16_t wrapped_first_sc = 0;
uint16_t wrapped_last_sc = 0;
if (last_sc >= ofdm_symbol_size) {
last_sc = ofdm_symbol_size - 1;
wrapped = true;
auto num_sc_left = num_sc - (last_sc - first_sc + 1);
wrapped_last_sc = wrapped_first_sc + num_sc_left - 1;
}
timestamp = time;
scopeGraphData_t *iq_header = scope_data->data.scope_graph_data;
len = iq_header->lineSz;
c16_t *source = (c16_t *)(iq_header + 1);
power.reserve(num_sc * num_symbols);
for (auto symbol = 0; symbol < num_symbols; symbol++) {
int subcarrier = 0;
for (auto sc = first_sc; sc <= last_sc; sc++) {
auto source_index = sc + symbol * ofdm_symbol_size;
power[subcarrier * num_symbols + symbol] = std::pow(source[source_index].r, 2) + std::pow(source[source_index].i, 2);
subcarrier++;
}
if (wrapped) {
for (auto sc = wrapped_first_sc; sc <= wrapped_last_sc; sc++) {
auto source_index = sc + symbol * ofdm_symbol_size;
power[subcarrier * num_symbols + symbol] = std::pow(source[source_index].r, 2) + std::pow(source[source_index].i, 2);
subcarrier++;
}
}
}
max = *std::max_element(power.begin(), power.end());
if (frozen && max > stop_at_min) {
next = false;
}
scope_data->is_data_ready = false;
}
}
}
void Draw(float time, int ofdm_symbol_size, int num_symbols, int first_carrier_offset, int num_rb)
{
ReadData(time, ofdm_symbol_size, num_symbols, first_carrier_offset, num_rb);
ImGui::BeginGroup();
if (ImGui::Button(frozen ? "Unfreeze" : "Freeze")) {
frozen = !frozen;
next = false;
}
if (frozen) {
ImGui::SameLine();
ImGui::BeginDisabled(next);
if (ImGui::Button("Load next data")) {
next = true;
}
ImGui::EndDisabled();
ImGui::Text("Filter parameters:");
ImGui::InputFloat("Max Power minimum", &stop_at_min, 10, 100);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Data with maximum power below that value will be discarded.");
}
}
static std::vector<int> symbol_boundaries = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
if (ImPlot::BeginPlot(label.c_str(), {(float)ImGui::GetWindowWidth() * 0.9f, 0})) {
auto num_sc = num_rb * NR_NB_SC_PER_RB;
ImPlot::SetupAxes("symbols", "subcarriers");
ImPlot::SetupAxisLimits(ImAxis_X1, num_symbols, 0);
ImPlot::SetupAxisLimits(ImAxis_Y1, num_sc, 0);
ImPlot::PlotHeatmap(label.c_str(),
power.data(),
num_sc,
num_symbols,
0,
max,
nullptr,
{0, 0},
{(double)num_symbols, (double)num_sc});
ImPlot::PlotInfLines("Symbol boundary", symbol_boundaries.data(), symbol_boundaries.size());
ImPlot::PlotInfLines("RB boundary", rb_boundaries.data(), num_rb, ImPlotInfLinesFlags_Horizontal);
ImPlot::EndPlot();
}
ImGui::SameLine();
ImPlot::ColormapScale("##HeatScale", 0, max);
ImGui::Text("Data is %.2f seconds old", time - timestamp);
ImGui::EndGroup();
}
};
// utility structure for realtime plot
struct ScrollingBuffer {
int MaxSize;
int Offset;
ImVector<ImVec2> Data;
ScrollingBuffer(int max_size = 2000)
{
MaxSize = max_size;
Offset = 0;
Data.reserve(MaxSize);
}
void AddPoint(float x, float y)
{
if (Data.size() < MaxSize)
Data.push_back(ImVec2(x, y));
else {
Data[Offset] = ImVec2(x, y);
Offset = (Offset + 1) % MaxSize;
}
}
void Erase()
{
if (Data.size() > 0) {
Data.shrink(0);
Offset = 0;
}
}
};
void ShowUeScope(void *data_void_ptr, float t)
{
PHY_VARS_NR_UE *ue = (PHY_VARS_NR_UE *)data_void_ptr;
ImGui::Begin("UE KPI");
if (ImPlot::BeginPlot("##Scrolling", ImVec2(-1, 150))) {
static float history = 10.0f;
ImGui::SliderFloat("History", &history, 1, 30, "%.1f s");
static ScrollingBuffer rbs_buffer;
static ScrollingBuffer bler;
static ScrollingBuffer mcs;
rbs_buffer.AddPoint(t, getKPIUE()->nofRBs);
bler.AddPoint(t, (float)getKPIUE()->nb_nack / (float)getKPIUE()->nb_total);
mcs.AddPoint(t, (float)getKPIUE()->dl_mcs);
ImPlot::SetupAxes("time", "noOfRbs");
ImPlot::SetupAxisLimits(ImAxis_X1, t - history, t, ImGuiCond_Always);
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, NR_MAX_RB);
ImPlot::SetupAxis(ImAxis_Y2, "bler [%]", ImPlotAxisFlags_AuxDefault);
ImPlot::SetupAxis(ImAxis_Y3, "MCS", ImPlotAxisFlags_AuxDefault);
ImPlot::SetAxes(ImAxis_X1, ImAxis_Y1);
ImPlot::PlotLine("noOfRbs", &rbs_buffer.Data[0].x, &rbs_buffer.Data[0].y, rbs_buffer.Data.size(), 0, 0, 2 * sizeof(float));
ImPlot::SetAxes(ImAxis_X1, ImAxis_Y2);
ImPlot::PlotLine("bler", &bler.Data[0].x, &bler.Data[0].y, bler.Data.size(), 0, 0, 2 * sizeof(float));
ImPlot::SetAxes(ImAxis_X1, ImAxis_Y3);
ImPlot::PlotLine("mcs", &mcs.Data[0].x, &mcs.Data[0].y, mcs.Data.size(), 0, 0, 2 * sizeof(float));
ImPlot::EndPlot();
}
ImGui::End();
if (ImGui::Begin("UE PDSCH IQ")) {
static auto iq_data = new IQData();
static auto pdsch_iq_hist = new IQHist("PDSCH IQ");
bool new_data = false;
if (pdsch_iq_hist->ShouldReadData()) {
new_data = iq_data->TryCollect(&scope_array[pdschRxdataF_comp], t, pdsch_iq_hist->GetEpsilon(), iq_procedure_timer);
}
pdsch_iq_hist->Draw(iq_data, t, new_data);
}
ImGui::End();
if (ImGui::Begin("Time domain samples")) {
static auto iq_data = new IQData();
// Issue with imgui deferring draw calls until the end of the frame - cases segfault if scatterplot has too many points
bool disable_scatterplot = true;
static auto time_domain_iq = new IQHist("Time domain samples", disable_scatterplot);
bool new_data = false;
if (time_domain_iq->ShouldReadData()) {
new_data = iq_data->TryCollect(&scope_array[ueTimeDomainSamples], t, time_domain_iq->GetEpsilon(), iq_procedure_timer);
}
time_domain_iq->Draw(iq_data, t, new_data);
}
ImGui::End();
if (ImGui::Begin("Time domain samples - before sync")) {
static auto iq_data = new IQData();
// Issue with imgui deferring draw calls until the end of the frame - cases segfault if scatterplot has too many points
bool disable_scatterplot = true;
static auto time_domain_iq = new IQHist("Time domain samples - before sync", disable_scatterplot);
bool new_data = false;
if (time_domain_iq->ShouldReadData()) {
new_data = iq_data->TryCollect(&scope_array[ueTimeDomainSamplesBeforeSync], t, time_domain_iq->GetEpsilon(), iq_procedure_timer);
}
time_domain_iq->Draw(iq_data, t, new_data);
}
ImGui::End();
if (ImGui::Begin("Broadcast channel")) {
ImGui::Text("RSRP %d", ue->measurements.ssb_rsrp_dBm[ue->frame_parms.ssb_index]);
if (ImGui::TreeNode("IQ")) {
static auto iq_data = new IQData();
static auto broadcast_iq_hist = new IQHist("Broadcast IQ");
bool new_data = false;
if (broadcast_iq_hist->ShouldReadData()) {
new_data = iq_data->TryCollect(&scope_array[ue->sl_mode ? psbchRxdataF_comp : pbchRxdataF_comp],
t,
broadcast_iq_hist->GetEpsilon(), iq_procedure_timer);
}
broadcast_iq_hist->Draw(iq_data, t, new_data);
ImGui::TreePop();
}
if (ImGui::TreeNode("CHest")) {
static auto chest_iq_data = new IQData();
static auto broadcast_iq_chest = new IQHist("Broadcast Chest");
bool new_data = false;
if (broadcast_iq_chest->ShouldReadData()) {
new_data = chest_iq_data->TryCollect(&scope_array[ue->sl_mode ? psbchDlChEstimateTime : pbchDlChEstimateTime],
t,
broadcast_iq_chest->GetEpsilon(), iq_procedure_timer);
}
broadcast_iq_chest->Draw(chest_iq_data, t, new_data);
ImGui::TreePop();
}
if (ImGui::TreeNode("LLR")) {
static auto llr_plot = new LLRPlot();
llr_plot->Draw(t, ue->sl_mode ? psbchLlr : pbchLlr, "Broadcast LLR");
ImGui::TreePop();
}
}
ImGui::End();
// if (ImGui::Begin("RX IQ")) {
// static auto common_rx_iq_heatmap = new IQSlotHeatmap(&scope_array[commonRxdataF], "common RX IQ");
// common_rx_iq_heatmap->Draw(t,
// ue->frame_parms.ofdm_symbol_size,
// ue->frame_parms.symbols_per_slot,
// ue->frame_parms.first_carrier_offset,
// ue->frame_parms.N_RB_DL);
// }
// ImGui::End();
}
void ShowGnbScope(void *data_void_ptr, float t)
{
(void)data_void_ptr;
// if (ImGui::TreeNode("RX IQ")) {
// static auto gnb_heatmap = new IQSlotHeatmap(&scope_array[gNBRxdataF], "common RX IQ");
// gnb_heatmap->Draw(t,
// gNB->frame_parms.ofdm_symbol_size,
// gNB->frame_parms.symbols_per_slot,
// gNB->frame_parms.first_carrier_offset,
// gNB->frame_parms.N_RB_UL);
// ImGui::TreePop();
// }
if (ImGui::Begin("PUSCH SLOT IQ")) {
static auto pusch_iq = new IQData();
static auto pusch_iq_display = new IQHist("PUSCH compensated IQ");
bool new_data = false;
if (pusch_iq_display->ShouldReadData()) {
new_data = pusch_iq->TryCollect(&scope_array[gNBPuschRxIq], t, pusch_iq_display->GetEpsilon(), iq_procedure_timer);
}
pusch_iq_display->Draw(pusch_iq, t, new_data);
}
ImGui::End();
if (ImGui::Begin("PUSCH LLRs")) {
static auto pusch_llr_plot = new LLRPlot();
pusch_llr_plot->Draw(t, gNBPuschLlr, "PUSCH LLR");
}
ImGui::End();
if (ImGui::Begin("Time domain samples")) {
static auto iq_data = new IQData();
// Issue with imgui deferring draw calls until the end of the frame - cases segfault if scatterplot has too many points
bool disable_scatterplot = true;
static auto time_domain_iq = new IQHist("Time domain samples", disable_scatterplot);
bool new_data = false;
if (time_domain_iq->ShouldReadData()) {
new_data = iq_data->TryCollect(&scope_array[gNbTimeDomainSamples], t, time_domain_iq->GetEpsilon(), iq_procedure_timer);
}
time_domain_iq->Draw(iq_data, t, new_data);
}
ImGui::End();
}
void ShowIQFileViewer(void *data_void_ptr)
{
auto iq_data = static_cast<std::vector<IQData> *>(data_void_ptr);
if (ImGui::Begin("Scope selection")) {
static int selected_scope = 0;
ImGui::Combo(
"Select scope",
&selected_scope,
[](void *userdata, int idx) {
std::vector<IQData> *iq_data = static_cast<std::vector<IQData>*>(userdata);
return scope_id_to_string(static_cast<scopeDataType>((*iq_data)[idx].scope_id));
},
iq_data,
iq_data->size());
static auto iq_display = new IQHist("IQ File Viewer");
iq_display->Draw(&(*iq_data)[selected_scope], 0, false);
}
ImGui::End();
}
void *imscope_thread(void *data_void_ptr)
{
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit())
return nullptr;
// Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
// GL ES 2.0 + GLSL 100
const char *glsl_version = "#version 100";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
#elif defined(__APPLE__)
// GL 3.2 + GLSL 150
const char *glsl_version = "#version 150";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
#else
// GL 3.0 + GLSL 130
const char *glsl_version = "#version 130";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
// glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only
#endif
// Create window with graphics context
GLFWwindow *window = glfwCreateWindow(1280, 720, "imscope", nullptr, nullptr);
if (window == nullptr)
return nullptr;
glfwMakeContextCurrent(window);
glfwSwapInterval(1); // For frame capping
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImPlot::CreateContext();
ImGuiIO &io = ImGui::GetIO();
(void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(window, true);
#ifdef __EMSCRIPTEN__
ImGui_ImplGlfw_InstallEmscriptenCallbacks(window, "#canvas");
#endif
ImGui_ImplOpenGL3_Init(glsl_version);
// Our state
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
for (auto i = 0U; i < NR_MAX_RB; i++) {
rb_boundaries.push_back(i * NR_NB_SC_PER_RB);
}
static double last_frame_time = glfwGetTime();
static int target_fps = 24;
bool is_ue = IS_SOFTMODEM_5GUE;
bool is_gnb = IS_SOFTMODEM_GNB;
bool close_window = false;
while (!glfwWindowShouldClose(window) && close_window == false) {
// Poll and handle events (inputs, window resize, etc.)
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy
// of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your
// copy of the keyboard data. Generally you may always pass all inputs to dear imgui, and hide them from your application based
// on those two flags.
glfwPollEvents();
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
static bool reset_ini_settings = false;
if (reset_ini_settings) {
ImGui::LoadIniSettingsFromDisk("imscope-init.ini");
reset_ini_settings = false;
}
ImGui::NewFrame();
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
static float t = 0;
static bool show_imgui_demo_window = false;
static bool show_implot_demo_window = false;
ImGui::DockSpaceOverViewport();
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("Close scope")) {
close_window = true;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Options")) {
ImGui::Checkbox("Show imgui demo window", &show_imgui_demo_window);
ImGui::Checkbox("Show implot demo window", &show_implot_demo_window);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Layout")) {
if (ImGui::MenuItem("Reset")) {
reset_ini_settings = true;
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
ImGui::Begin("Status bar");
ImGui::Text("Total time used by IQ capture procedures per milisecond: %.2f [us]/[ms]", iq_procedure_timer.average / 1000);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Total time used in PHY threads for copying out IQ data for the scope, in uS, averaged over 1 ms");
}
ImGui::End();
ImGui::Begin("Global scope settings");
ImGui::ShowStyleSelector("ImGui Style");
ImPlot::ShowStyleSelector("ImPlot Style");
ImPlot::ShowColormapSelector("ImPlot Colormap");
ImGui::SliderInt("FPS target", &target_fps, 12, 60);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Reduces scope flickering in unfrozen mode. Can reduce impact on perfromance of the modem");
}
ImGui::End();
t += ImGui::GetIO().DeltaTime;
iq_procedure_timer.UpdateAverage(t);
if (is_ue) {
ShowUeScope(data_void_ptr, t);
} else if (is_gnb) {
ShowGnbScope(data_void_ptr, t);
} else {
ShowIQFileViewer(data_void_ptr);
}
// For reference
if (show_implot_demo_window)
ImPlot::ShowDemoWindow();
if (show_imgui_demo_window)
ImGui::ShowDemoWindow();
// Rendering
ImGui::Render();
glViewport(0, 0, display_w, display_h);
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
double target_frame_time = 1.0 / target_fps;
double delta = glfwGetTime() - last_frame_time;
if (delta < target_frame_time) {
std::this_thread::sleep_for(std::chrono::duration<float>(target_frame_time - delta));
}
glfwSwapBuffers(window);
last_frame_time = glfwGetTime();
}
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return nullptr;
}