ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | README | LICENSE

Commit: 4b9b2163f207aff04c9f21c6dc195ed8ff92436f
Parent: 2e0da11544625ff32042362c215e49d45492e27c
Author: Randy Palamar
Date:   Mon,  1 Dec 2025 21:26:59 -0700

ui: add acquistion kind live control

If the live imaging parameters indicate that there are multiple
kinds of sequences programmed an additional menu will be enabled
allowing the user to switch between them. This is currently
implemented in a fairly hacky/inflexible way and can be greatly
improved with better imaging system integration in the future
(especially if the beamformer drives the programming).

Diffstat:
Mbeamformer.meta | 1+
Mbeamformer_parameters.h | 22+++++++++++++---------
Mbeamformer_shared_memory.c | 2+-
Mgenerated/beamformer.meta.c | 4++++
Mtests/throughput.c | 8+++++++-
Mui.c | 92++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
6 files changed, 103 insertions(+), 26 deletions(-)

diff --git a/beamformer.meta b/beamformer.meta @@ -156,6 +156,7 @@ [EPIC_UHERCULES EPIC-UHERCULES 0] [Flash Flash 0] [HERO_PA HERO-PA 0] + [ULM ULM 0] } @Expand(AcquisitionKindTable) @Enumeration(`$(name)`) AcquisitionKind diff --git a/beamformer_parameters.h b/beamformer_parameters.h @@ -38,18 +38,22 @@ typedef enum { X(TGCControlPoints, 2) \ X(SaveData, 3) \ X(SaveNameTag, 4) \ - X(StopImaging, 5) + X(StopImaging, 5) \ + X(AcquisitionKind, 6) \ + /* NOTE(rnp): if this exceeds 32 you need to fix the flag handling code */ #define BEAMFORMER_LIVE_IMAGING_PARAMETERS_LIST \ - X(active, uint32_t, , 1) \ - X(save_enabled, uint32_t, , 1) \ - X(save_active, uint32_t, , 1) \ - X(transmit_power, float, , 1) \ - X(image_plane_offsets, float, [BeamformerViewPlaneTag_Count], BeamformerViewPlaneTag_Count) \ - X(tgc_control_points, float, [8], 8) \ - X(save_name_tag_length, int32_t, , 1) \ - X(save_name_tag, char, [128], 128) + X(active, uint32_t, , 1) \ + X(save_enabled, uint32_t, , 1) \ + X(save_active, uint32_t, , 1) \ + X(acquisition_kind, uint32_t, , 1) \ + X(acquisition_kind_enabled_flags, uint64_t, , 1) \ + X(transmit_power, float, , 1) \ + X(image_plane_offsets, float, [BeamformerViewPlaneTag_Count], BeamformerViewPlaneTag_Count) \ + X(tgc_control_points, float, [8], 8) \ + X(save_name_tag_length, int32_t, , 1) \ + X(save_name_tag, char, [128], 128) #define X(name, type, size, ...) type name size; typedef struct {BEAMFORMER_LIVE_IMAGING_PARAMETERS_LIST} BeamformerLiveImagingParameters; diff --git a/beamformer_shared_memory.c b/beamformer_shared_memory.c @@ -1,5 +1,5 @@ /* See LICENSE for license details. */ -#define BEAMFORMER_SHARED_MEMORY_VERSION (31UL) +#define BEAMFORMER_SHARED_MEMORY_VERSION (32UL) typedef enum { BeamformerWorkKind_Compute, diff --git a/generated/beamformer.meta.c b/generated/beamformer.meta.c @@ -92,6 +92,7 @@ typedef enum { BeamformerAcquisitionKind_EPIC_UHERCULES = 9, BeamformerAcquisitionKind_Flash = 10, BeamformerAcquisitionKind_HERO_PA = 11, + BeamformerAcquisitionKind_ULM = 12, BeamformerAcquisitionKind_Count, } BeamformerAcquisitionKind; @@ -486,6 +487,7 @@ read_only global u8 beamformer_acquisition_kind_has_fixed_transmits[] = { 0, 0, 0, + 0, }; read_only global s8 beamformer_acquisition_kind_strings[] = { @@ -501,6 +503,7 @@ read_only global s8 beamformer_acquisition_kind_strings[] = { s8_comp("EPIC-UHERCULES"), s8_comp("Flash"), s8_comp("HERO-PA"), + s8_comp("ULM"), }; read_only global s8 beamformer_filter_kind_strings[] = { @@ -636,6 +639,7 @@ read_only global s8 beamformer_shader_global_header_strings[] = { "#define AcquisitionKind_EPIC_UHERCULES 9\n" "#define AcquisitionKind_Flash 10\n" "#define AcquisitionKind_HERO_PA 11\n" + "#define AcquisitionKind_ULM 12\n" "\n"), s8_comp("" "#define InterpolationMode_Nearest 0\n" diff --git a/tests/throughput.c b/tests/throughput.c @@ -518,7 +518,13 @@ execute_study(Arena arena, Stream path, Options *options) } if (options->loop) { - BeamformerLiveImagingParameters lip = {.active = 1, .save_enabled = 1}; + BeamformerLiveImagingParameters lip = { + .active = 1, + .acquisition_kind = bp.acquisition_kind, + .save_enabled = 1, + .acquisition_kind_enabled_flags = 1 << bp.acquisition_kind, + }; + s8 short_name = s8("Throughput"); mem_copy(lip.save_name_tag, short_name.data, (uz)short_name.len); lip.save_name_tag_length = (i32)short_name.len; diff --git a/ui.c b/ui.c @@ -247,6 +247,8 @@ typedef enum { UI_BID_VIEW_CLOSE, GLOBAL_MENU_BUTTONS FRAME_VIEW_BUTTONS + UI_BID_ACQUISITION_KIND_FIRST, + UI_BID_ACQUISITION_KIND_LAST = UI_BID_ACQUISITION_KIND_FIRST + BeamformerAcquisitionKind_Count - 1, } UIButtonID; #undef X @@ -376,6 +378,7 @@ struct BeamformerFrameView { typedef struct BeamformerLiveControlsView BeamformerLiveControlsView; struct BeamformerLiveControlsView { + Variable acquisition_menu; Variable transmit_power; Variable tgc_control_points[countof(((BeamformerLiveImagingParameters *)0)->tgc_control_points)]; Variable save_button; @@ -392,6 +395,7 @@ typedef enum { InteractionKind_Auto, InteractionKind_Button, InteractionKind_Drag, + InteractionKind_AcquisitionMenu, InteractionKind_Menu, InteractionKind_Ruler, InteractionKind_Scroll, @@ -1500,6 +1504,10 @@ add_live_controls_view(BeamformerUI *ui, Variable *parent, Arena *arena) Variable *view = result->view.child; BeamformerLiveControlsView *lv = view->generic = push_struct(arena, typeof(*lv)); + Variable *amenu = &lv->acquisition_menu; + fill_variable(amenu, view, s8(""), V_INPUT, VT_GROUP, ui->small_font); + amenu->group.kind = VariableGroupKind_List; + fill_variable(&lv->transmit_power, view, s8(""), V_INPUT|V_LIVE_CONTROL, VT_BEAMFORMER_VARIABLE, ui->small_font); fill_beamformer_variable(&lv->transmit_power, s8(""), &lip->transmit_power, (v2){{0, 1.0f}}, 100.0f, 0.05f); @@ -2965,6 +2973,31 @@ draw_live_controls_view(BeamformerUI *ui, Variable *var, Rect r, v2 mouse, Arena v2 at = {{text_off, r.pos.y}}; + if (popcount_u64(lip->acquisition_kind_enabled_flags) > 1) { + u32 kind = lip->acquisition_kind; + s8 kind_string = kind < BeamformerAcquisitionKind_Count ? beamformer_acquisition_kind_strings[kind] + : s8("Invalid"); + v2 label_size = draw_text(s8("Acquisition:"), at, &text_spec); + + text_spec.colour = v4_lerp(FG_COLOUR, HOVERED_COLOUR, lv->acquisition_menu.hover_t); + text_spec.limits.size.w -= label_size.x + (f32)ui->small_font.baseSize / 2; + + Rect menu = {.pos = at}; + menu.pos.x += label_size.x + (f32)ui->small_font.baseSize / 2; + menu.size.h = (f32)ui->small_font.baseSize + TITLE_BAR_PAD; + menu.size.w = draw_text(kind_string, menu.pos, &text_spec).x; + + text_spec.colour = FG_COLOUR; + text_spec.limits.size.w = r.size.w - (text_off - r.pos.x); + + Interaction interaction = {.kind = InteractionKind_AcquisitionMenu, + .var = &lv->acquisition_menu, + .rect = menu}; + hover_interaction(ui, mouse, interaction); + + at.y += label_size.y; + } + v4 hsv_power_slider = {{0.35f * ease_in_out_cubic(1.0f - lip->transmit_power), 0.65f, 0.65f, 1}}; at.y += draw_text(s8("Power:"), at, &text_spec).y; at.x = slider_off; @@ -3638,20 +3671,30 @@ function void ui_button_interaction(BeamformerUI *ui, Variable *button) { assert(button->type == VT_UI_BUTTON); - switch (button->button) { - case UI_BID_VIEW_CLOSE:{ ui_view_close(ui, button->parent); }break; - case UI_BID_FV_COPY_HORIZONTAL:{ - ui_copy_frame(ui, button->parent->parent, RSD_HORIZONTAL); - }break; - case UI_BID_FV_COPY_VERTICAL:{ - ui_copy_frame(ui, button->parent->parent, RSD_VERTICAL); - }break; - case UI_BID_GM_OPEN_VIEW_RIGHT:{ - ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL, BeamformerFrameViewKind_Latest); - }break; - case UI_BID_GM_OPEN_VIEW_BELOW:{ - ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL, BeamformerFrameViewKind_Latest); - }break; + if (Between(button->button, UI_BID_ACQUISITION_KIND_FIRST, UI_BID_ACQUISITION_KIND_LAST)) { + BeamformerSharedMemory *sm = ui->shared_memory; + BeamformerLiveImagingParameters *lip = &sm->live_imaging_parameters; + + lip->acquisition_kind = button->button - UI_BID_ACQUISITION_KIND_FIRST; + atomic_or_u32(&sm->live_imaging_dirty_flags, BeamformerLiveImagingDirtyFlags_AcquisitionKind); + ui_view_close(ui, button->parent->group.container); + } else { + switch (button->button) { + case UI_BID_VIEW_CLOSE:{ ui_view_close(ui, button->parent); }break; + case UI_BID_FV_COPY_HORIZONTAL:{ + ui_copy_frame(ui, button->parent->parent, RSD_HORIZONTAL); + }break; + case UI_BID_FV_COPY_VERTICAL:{ + ui_copy_frame(ui, button->parent->parent, RSD_VERTICAL); + }break; + case UI_BID_GM_OPEN_VIEW_RIGHT:{ + ui_add_live_frame_view(ui, button->parent->parent, RSD_HORIZONTAL, BeamformerFrameViewKind_Latest); + }break; + case UI_BID_GM_OPEN_VIEW_BELOW:{ + ui_add_live_frame_view(ui, button->parent->parent, RSD_VERTICAL, BeamformerFrameViewKind_Latest); + }break; + InvalidDefaultCase; + } } } @@ -3859,12 +3902,31 @@ ui_end_interact(BeamformerUI *ui, v2 mouse) InvalidDefaultCase; } }break; - case InteractionKind_Menu:{ + case InteractionKind_AcquisitionMenu: + case InteractionKind_Menu: + { assert(it->var->type == VT_GROUP); VariableGroup *g = &it->var->group; if (g->container) { ui_widget_bring_to_front(&ui->floating_widget_sentinal, g->container); } else { + if (it->kind == InteractionKind_AcquisitionMenu) { + for (Variable *var = g->first; var;) { + Variable *next = var->next; + var->next = var->parent = 0; + ui_variable_free(ui, var); + var = next; + } + g->first = g->last = 0; + + BeamformerLiveImagingParameters *lip = &ui->shared_memory->live_imaging_parameters; + u64 enabled_kinds = atomic_load_u64(&lip->acquisition_kind_enabled_flags); + for EachBit(enabled_kinds, kind) { + Variable *button = add_variable(ui, it->var, &ui->arena, beamformer_acquisition_kind_strings[kind], + V_INPUT, VT_UI_BUTTON, ui->small_font); + button->button = UI_BID_ACQUISITION_KIND_FIRST + kind; + } + } g->container = add_floating_view(ui, &ui->arena, VT_UI_MENU, mouse, it->var, 1); } }break;