diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 26acc69..d37a31c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,15 +17,15 @@ jobs: config: - { name: "Ubuntu Low (AppImage)", - os: "ubuntu-20.04", - cc: "gcc-10", - cxx: "g++-10" + os: "ubuntu-22.04", + cc: "gcc-11", + cxx: "g++-11" } - { name: "Ubuntu High", - os: "ubuntu-22.04", - cc: "clang-14", - cxx: "clang++-14" + os: "ubuntu-24.04", + cc: "clang-18", + cxx: "clang++-18" } fail-fast: false env: @@ -42,13 +42,13 @@ jobs: - name: Update apt run: sudo apt update - name: Install missing packages - run: sudo apt install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libglu1-mesa-dev freeglut3-dev mesa-common-dev + run: sudo apt install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libglu1-mesa-dev freeglut3-dev libfuse2 mesa-common-dev libwayland-bin libwayland-dev libxkbcommon-dev xorg-dev - name: Check config run: | $CC --version $CXX --version - name: Configure - run: cmake -S . -B $bld_dir -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC --install-prefix $app_dir/usr -DARB_ARCH=x86-64-v2 + run: cmake -S . -B $bld_dir -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC --install-prefix $app_dir/usr -DARB_ARCH=haswell - name: Build run: cmake --build $bld_dir -j 2 - name: Install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a5ef47..3da403a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -97,7 +97,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Get artifacts" - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Move and rename appimage run: mv */*.AppImage arbor-gui.AppImage - name: Move and rename dmg @@ -122,4 +122,3 @@ jobs: generateReleaseNotes: true artifacts: '*.dmg,*.AppImage,*.tar.gz' token: ${{ secrets.GITHUB_TOKEN }} - \ No newline at end of file diff --git a/3rd-party/ImGuizmo b/3rd-party/ImGuizmo index 664cf2d..ba662b1 160000 --- a/3rd-party/ImGuizmo +++ b/3rd-party/ImGuizmo @@ -1 +1 @@ -Subproject commit 664cf2d73864a36b2a8b5091d33fc4578c885eca +Subproject commit ba662b119d64f9ab700bb2cd7b2781f9044f5565 diff --git a/3rd-party/arbor b/3rd-party/arbor index 8e82ec1..c4ff08c 160000 --- a/3rd-party/arbor +++ b/3rd-party/arbor @@ -1 +1 @@ -Subproject commit 8e82ec1947a3a6d84452969d1d628b2292654319 +Subproject commit c4ff08ceef2760796538c4117a259fd2104442ed diff --git a/3rd-party/fmt b/3rd-party/fmt index c4ee726..1239137 160000 --- a/3rd-party/fmt +++ b/3rd-party/fmt @@ -1 +1 @@ -Subproject commit c4ee726532178e556d923372f29163bd206d7732 +Subproject commit 123913715afeb8a437e6388b4473fcc4753e1c9a diff --git a/3rd-party/glbinding b/3rd-party/glbinding index 21729a0..ff2ff7a 160000 --- a/3rd-party/glbinding +++ b/3rd-party/glbinding @@ -1 +1 @@ -Subproject commit 21729a0f5e6f64565d708f6221d436662c467325 +Subproject commit ff2ff7a7aad77b907a67bd5962d6e11a4f9c699e diff --git a/3rd-party/glfw b/3rd-party/glfw index 7482de6..e7ea71b 160000 --- a/3rd-party/glfw +++ b/3rd-party/glfw @@ -1 +1 @@ -Subproject commit 7482de6071d21db77a7236155da44c172a7f6c9e +Subproject commit e7ea71be039836da3a98cea55ae5569cb5eb885c diff --git a/3rd-party/glm b/3rd-party/glm index bf71a83..0af55cc 160000 --- a/3rd-party/glm +++ b/3rd-party/glm @@ -1 +1 @@ -Subproject commit bf71a834948186f4097caa076cd2663c69a10e1e +Subproject commit 0af55ccecd98d4e5a8d1fad7de25ba429d60e863 diff --git a/3rd-party/icons b/3rd-party/icons index 7d6ff1f..f30b1e7 160000 --- a/3rd-party/icons +++ b/3rd-party/icons @@ -1 +1 @@ -Subproject commit 7d6ff1f4ba51e7a2b142be39457768abece1549c +Subproject commit f30b1e73b2d71eb331d77619c3f1de34199afc38 diff --git a/3rd-party/imgui b/3rd-party/imgui index 8cbd391..11b3a7c 160000 --- a/3rd-party/imgui +++ b/3rd-party/imgui @@ -1 +1 @@ -Subproject commit 8cbd391f096b9314a08670052cc0025cbcadb249 +Subproject commit 11b3a7c8ca23201294464c7f368614a9106af2a1 diff --git a/3rd-party/implot b/3rd-party/implot index b47c8ba..18c7243 160000 --- a/3rd-party/implot +++ b/3rd-party/implot @@ -1 +1 @@ -Subproject commit b47c8bacdbc78bc521691f70666f13924bb522ab +Subproject commit 18c72431f8265e2b0b5378a3a73d8a883b2175ff diff --git a/3rd-party/json b/3rd-party/json index 69d7448..9cca280 160000 --- a/3rd-party/json +++ b/3rd-party/json @@ -1 +1 @@ -Subproject commit 69d744867f8847c91a126fa25e9a6a3d67b3be41 +Subproject commit 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 diff --git a/3rd-party/spdlog b/3rd-party/spdlog index 76fb40d..f355b3d 160000 --- a/3rd-party/spdlog +++ b/3rd-party/spdlog @@ -1 +1 @@ -Subproject commit 76fb40d95455f249bd70824ecfcae7a8f0930fa3 +Subproject commit f355b3d58f7067eee1706ff3c801c2361011f3d5 diff --git a/CMakeLists.txt b/CMakeLists.txt index d238a21..22bade5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,13 @@ -cmake_minimum_required(VERSION 3.19) +cmake_minimum_required(VERSION 3.27) find_package(Git) project(the-arbor-gui - VERSION 0.8.1 + VERSION 0.11.1 LANGUAGES C CXX) +set(EXPORT_COMPILE_COMMANDS ON) + set(CMAKE_CXX_STANDARD 20) include(GNUInstallDirs) @@ -141,6 +143,7 @@ target_include_directories(imgui PRIVATE 3rd-party/imgui) target_include_directories(imgui PUBLIC 3rd-party/implot) target_include_directories(imgui PUBLIC 3rd-party/ImGuizmo/) target_link_libraries(imgui PRIVATE glfw) +target_compile_definitions(arbor-gui-deps INTERFACE SPDLOG_FMT_EXTERNAL) # Get commit hashes from git and inject into config execute_process(COMMAND ${GIT_EXECUTABLE} -C ${PROJECT_SOURCE_DIR} describe --always --dirty --abbrev=0 diff --git a/data/ball_and_stick.swc b/data/ball_and_stick.swc index c223837..89c99da 100644 --- a/data/ball_and_stick.swc +++ b/data/ball_and_stick.swc @@ -1,2 +1,4 @@ -1 1 -3.0 0.0 0.0 3 -1 -2 1 3.0 0.0 0.0 3 1 +# SWC format: ID, Type, X, Y, Z, Radius, Parent +1 1 0.0 0.0 0.0 10.0 -1 # Soma +2 3 0.0 0.0 10.0 1.0 1 # Dendrite +3 3 0.0 0.0 20.0 1.0 2 # Dendrite diff --git a/data/swc-interpretation/normal-soma-normal-dend.swc b/data/swc-interpretation/normal-soma-normal-dend.swc new file mode 100644 index 0000000..7f4e908 --- /dev/null +++ b/data/swc-interpretation/normal-soma-normal-dend.swc @@ -0,0 +1,8 @@ +# id, tag, x, y, z, radius, parent +1 1 -3.0 0.0 0.0 3.0 -1 +2 1 -3.0 0.0 3.0 3.0 1 +3 3 -3.0 -6.0 0.0 3.0 1 +4 3 -3.0 -9.0 0.0 3.0 3 +5 2 -3.0 6.0 0.0 3.0 1 +6 2 -3.0 12.0 0.0 3.0 5 +7 2 -3.0 18.0 0.0 3.0 6 diff --git a/data/swc-interpretation/normal-soma-single-dend.swc b/data/swc-interpretation/normal-soma-single-dend.swc new file mode 100644 index 0000000..5f03a98 --- /dev/null +++ b/data/swc-interpretation/normal-soma-single-dend.swc @@ -0,0 +1,7 @@ +# id, tag, x, y, z, radius, parent +1 1 -3.0 0.0 0.0 3.0 -1 +2 1 3.0 0.0 0.0 3.0 1 +3 3 -3.0 -6.0 0.0 3.0 1 +4 2 -3.0 6.0 0.0 3.0 1 +5 2 -3.0 12.0 0.0 3.0 4 +6 2 -3.0 18.0 0.0 3.0 5 diff --git a/data/swc-interpretation/single-soma-normal-dend.swc b/data/swc-interpretation/single-soma-normal-dend.swc new file mode 100644 index 0000000..ce42adf --- /dev/null +++ b/data/swc-interpretation/single-soma-normal-dend.swc @@ -0,0 +1,7 @@ +# id, tag, x, y, z, radius, parent +1 1 -3.0 0.0 0.0 3.0 -1 +2 3 -3.0 -6.0 0.0 3.0 1 +3 3 -3.0 -9.0 0.0 3.0 2 +4 2 -3.0 6.0 0.0 3.0 1 +5 2 -3.0 12.0 0.0 3.0 4 +6 2 -3.0 18.0 0.0 3.0 5 diff --git a/data/swc-interpretation/single-soma-single-dend.swc b/data/swc-interpretation/single-soma-single-dend.swc new file mode 100644 index 0000000..e1c7835 --- /dev/null +++ b/data/swc-interpretation/single-soma-single-dend.swc @@ -0,0 +1,6 @@ +# id, tag, x, y, z, radius, parent +1 1 -3.0 0.0 0.0 3.0 -1 +2 3 -3.0 -6.0 0.0 3.0 1 +3 2 -3.0 6.0 0.0 3.0 1 +4 2 -3.0 12.0 0.0 3.0 3 +5 2 -3.0 18.0 0.0 3.0 4 diff --git a/default.ini b/default.ini index 60f7193..e4bb40a 100644 --- a/default.ini +++ b/default.ini @@ -10,43 +10,43 @@ Collapsed=0 [Window][ Locations] Pos=0,24 -Size=339,696 +Size=338,696 Collapsed=0 DockId=0x00000001,0 [Window][Cell] -Pos=341,24 -Size=939,559 +Pos=340,24 +Size=940,559 Collapsed=0 -DockId=0x00000003,0 +DockId=0x00000002,0 [Window][Traces] -Pos=341,24 -Size=939,559 +Pos=340,24 +Size=940,559 Collapsed=0 -DockId=0x00000003,1 +DockId=0x00000002,1 [Window][ Morphology##info] -Pos=341,585 -Size=480,135 +Pos=340,585 +Size=603,135 Collapsed=0 -DockId=0x00000005,0 +DockId=0x00000004,0 [Window][ Locations##info] -Pos=823,585 -Size=457,135 +Pos=945,585 +Size=335,135 Collapsed=0 -DockId=0x00000006,0 +DockId=0x00000005,0 [Window][ Parameters] Pos=0,24 -Size=339,696 +Size=338,696 Collapsed=0 DockId=0x00000001,1 [Window][ Simulation] Pos=0,24 -Size=339,696 +Size=338,696 Collapsed=0 DockId=0x00000001,2 @@ -66,11 +66,12 @@ Size=758,446 Collapsed=0 [Docking][Data] -DockSpace ID=0x3BC79352 Window=0x4647B76E Pos=0,24 Size=1280,696 Split=X Selected=0xBF700C0A - DockNode ID=0x00000001 Parent=0x3BC79352 SizeRef=339,696 CentralNode=1 Selected=0xBF700C0A - DockNode ID=0x00000002 Parent=0x3BC79352 SizeRef=939,696 Split=Y Selected=0x90C8F40C - DockNode ID=0x00000003 Parent=0x00000002 SizeRef=939,559 Selected=0x65F68FF8 - DockNode ID=0x00000004 Parent=0x00000002 SizeRef=939,135 Split=X Selected=0xAB9526FD - DockNode ID=0x00000005 Parent=0x00000004 SizeRef=480,135 Selected=0x93CAA630 - DockNode ID=0x00000006 Parent=0x00000004 SizeRef=457,135 Selected=0xAB9526FD +DockSpace ID=0x3BC79352 Pos=0,24 Size=1280,696 CentralNode=1 Selected=0xBF700C0A +DockSpace ID=0xC0DFADC4 Window=0xD0388BC8 Pos=0,24 Size=1280,696 Split=X Selected=0x9FA67BE9 + DockNode ID=0x00000001 Parent=0xC0DFADC4 SizeRef=338,696 Selected=0xBB245857 + DockNode ID=0x00000006 Parent=0xC0DFADC4 SizeRef=940,696 Split=Y + DockNode ID=0x00000002 Parent=0x00000006 SizeRef=1280,559 CentralNode=1 Selected=0x47506C1A + DockNode ID=0x00000003 Parent=0x00000006 SizeRef=1280,135 Split=X Selected=0x29E5A105 + DockNode ID=0x00000004 Parent=0x00000003 SizeRef=821,135 Selected=0x29E5A105 + DockNode ID=0x00000005 Parent=0x00000003 SizeRef=457,135 Selected=0x655FAFBA diff --git a/src/geometry.cpp b/src/geometry.cpp index 373c377..9424461 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -699,12 +699,14 @@ void geometry::load_geometry(const arb::morphology& morph, bool reset) { branch_to_ids[branch].emplace_back(lo, hi); } } + log_info("Loaded {} segments and {} branches", segments.size(), morph.num_branches()); log_info("Making geometry"); if (segments.empty()) { log_info("Empty geometry"); return; } + root = {(float) segments[0].prox.x, (float) segments[0].prox.y, (float) segments[0].prox.z}; log_debug("New root x={} y={} z={}", root.x, root.y, root.z); { @@ -724,6 +726,7 @@ void geometry::load_geometry(const arb::morphology& morph, bool reset) { } log_debug("Frustra generated: {} ({} points)", indices.size()/n_indices, vertices.size()); + { // Re-scale into [-1, 1]^3 box rescale = ax.scale; diff --git a/src/gui_state.cpp b/src/gui_state.cpp index 02391a0..ac92881 100644 --- a/src/gui_state.cpp +++ b/src/gui_state.cpp @@ -1,7 +1,6 @@ #include "gui_state.hpp" #include -#include #include #include @@ -24,6 +23,7 @@ #include #include #include +#include #include "gui.hpp" #include "utils.hpp" @@ -37,6 +37,7 @@ extern float delta_zoom; extern glm::vec2 mouse; using namespace std::literals; +namespace U = arb::units; namespace { inline void gui_read_morphology(gui_state& state, bool& open); @@ -127,16 +128,14 @@ namespace { loader_error.clear(); ImGui::CloseCurrentPopup(); } - ImGui::EndPopup(); - } + } // load error if (ko) open = false; ImGui::EndPopup(); - } + } // Load } inline void gui_read_cat(gui_state& state, bool& open) { - with_id id{"loading cat"}; ImGui::OpenPopup("Load"); static std::vector suffixes{".so"}; @@ -249,7 +248,6 @@ namespace { } inline void gui_main(gui_state& state) { - static bool opt_fullscreen = true; static bool opt_padding = false; static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; @@ -291,12 +289,12 @@ namespace { // DockSpace ImGuiIO& io = ImGui::GetIO(); - if (io.ConfigFlags& ImGuiConfigFlags_DockingEnable) { + if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) { ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); } gui_menu_bar(state); - ImGui::End(); + ImGui::End(); // dockspace } inline void gui_read_morphology(gui_state& state, bool& open_file) { @@ -375,174 +373,183 @@ namespace { inline bool gui_axes(axes& ax) { ImGui::Text("%s Axes", icon_axes); gui_right_margin(); - gui_toggle(icon_on, icon_off, ax.active); + gui_toggle(fmt::format("{}##{}", icon_on, "ax").c_str(), fmt::format("{}##{}", icon_off, "ax").c_str(), ax.active); auto mv = ImGui::InputFloat3("Position", &ax.origin[0]); auto sz = ImGui::InputFloat("Size", &ax.scale, 0, 0, "%f µm"); return mv || sz; } - inline void gui_cell(gui_state& state) { - if (ImGui::Begin("Cell")) { - ImGui::BeginChild("Cell Render"); - auto size = ImGui::GetWindowSize(), win_pos = ImGui::GetWindowPos(); - auto& vs = state.view; - vs.size = to_glmvec(size); - state.renderer.render(vs, {mouse.x - win_pos.x, size.y + win_pos.y - mouse.y}); - ImGui::Image(reinterpret_cast(state.renderer.cell.tex), size, ImVec2(0, 1), ImVec2(1, 0)); - if (ImGui::IsItemHovered()) { - auto shft = ImGui::IsKeyDown(GLFW_KEY_LEFT_SHIFT) || ImGui::IsKeyDown(GLFW_KEY_RIGHT_SHIFT); - auto ctrl = ImGui::IsKeyDown(GLFW_KEY_LEFT_CONTROL) || ImGui::IsKeyDown(GLFW_KEY_RIGHT_CONTROL); - if (shft || ctrl) { - auto what = shft ? ImGuizmo::ROTATE : ImGuizmo::TRANSLATE; - glm::vec3 shift = {vs.offset.x/vs.size.x, vs.offset.y/vs.size.y, 0.0f}; - glm::mat4 V = glm::lookAt(vs.camera, vs.target/state.renderer.rescale + shift, vs.up); - glm::mat4 P = glm::perspective(glm::radians(vs.zoom), vs.size.x/vs.size.y, 0.1f, 100.0f); - ImGuizmo::SetDrawlist(); - ImGuizmo::SetRect(win_pos.x, win_pos.y, size.x, size.y); - ImGuizmo::Manipulate(glm::value_ptr(V), glm::value_ptr(P), what, ImGuizmo::LOCAL, glm::value_ptr(vs.rotate)); - } else { - vs.zoom = std::clamp(vs.zoom + delta_zoom, 1.0f, 45.0f); - } - } - - static float t_last = 0.0; - float t_now = glfwGetTime(); - if (state.demo_mode) vs.rotate = glm::rotate(vs.rotate, state.auto_omega*(t_now - t_last), glm::vec3{0.0f, 1.0f, 0.0f}); - t_last = t_now; - - state.object = state.renderer.get_id(); - - if (ImGui::BeginPopupContextWindow()) { - ImGui::Text("%s Camera", icon_camera); + inline void gui_cell_context_menu(gui_state& state) { + if (ImGui::BeginPopupContextWindow()) { + ImGui::Text("%s Camera", icon_camera); + { + with_indent indent{}; + ImGui::SliderFloat("Auto-rotate", &state.auto_omega, 0.1f, 2.0f); + gui_right_margin(); + gui_toggle(icon_on, icon_off, state.demo_mode); + ImGui::InputFloat3("Target", &state.view.target[0]); + ImGui::ColorEdit3("Background",& (state.renderer.cell.clear_color.x), ImGuiColorEditFlags_NoInputs); { - with_indent indent{}; - ImGui::SliderFloat("Auto-rotate", &state.auto_omega, 0.1f, 2.0f); - gui_right_margin(); - gui_toggle(icon_on, icon_off, state.demo_mode); - ImGui::InputFloat3("Target", &state.view.target[0]); - ImGui::ColorEdit3("Background",& (state.renderer.cell.clear_color.x), ImGuiColorEditFlags_NoInputs); - { - ImGui::SameLine(); - if (ImGui::BeginCombo("Colormap", state.renderer.cmap.c_str())) { - with_item_width iw(80.0f); - for (const auto& [k, v]: state.renderer.cmaps) { - if (ImGui::Selectable(k.c_str(), k == state.renderer.cmap)) state.renderer.cmap = k; - } - ImGui::EndCombo(); + ImGui::SameLine(); + if (ImGui::BeginCombo("Colormap", state.renderer.cmap.c_str())) { + with_item_width iw(80.0f); + for (const auto& [k, v]: state.renderer.cmaps) { + if (ImGui::Selectable(k.c_str(), k == state.renderer.cmap)) state.renderer.cmap = k; } + ImGui::EndCombo(); } - if (ImGui::BeginMenu(fmt::format("{} Snap", icon_locset).c_str())) { - for (const auto& id: state.locsets) { - const auto& ls = state.locset_defs[id]; - if (ls.state != def_state::good) continue; - if (ImGui::BeginMenu(fmt::format("{} {}", icon_locset, ls.name).c_str())) { - auto points = state.builder.make_points(ls.data.value()); - for (const auto& point: points) { - const auto lbl = fmt::format("({: 7.3f} {: 7.3f} {: 7.3f})", point.x, point.y, point.z); - if (ImGui::MenuItem(lbl.c_str())) { - state.view.offset = {0.0, 0.0}; - state.view.target = point; - } + } + if (ImGui::BeginMenu(fmt::format("{} Snap", icon_locset).c_str())) { + for (const auto& id: state.locsets) { + const auto& ls = state.locset_defs[id]; + if (ls.state != def_state::good) continue; + if (ImGui::BeginMenu(fmt::format("{} {}", icon_locset, ls.name).c_str())) { + auto points = state.builder.make_points(ls.data.value()); + for (const auto& point: points) { + const auto lbl = fmt::format("({: 7.3f} {: 7.3f} {: 7.3f})", point.x, point.y, point.z); + if (ImGui::MenuItem(lbl.c_str())) { + state.view.offset = {0.0, 0.0}; + state.view.target = point; } - ImGui::EndMenu(); } + ImGui::EndMenu(); } - ImGui::EndMenu(); - } - if (gui_menu_item("Reset##camera", icon_refresh)) { - state.view.offset = {0.0f, 0.0f}; - state.view.rotate = glm::mat4(1.0f); - state.view.target = {0.0f, 0.0f, 0.0f}; - state.renderer.cell.clear_color = {214.0f/255, 214.0f/255, 214.0f/255}; } + ImGui::EndMenu(); } - ImGui::Separator(); - if(gui_axes(state.renderer.ax)) state.renderer.make_ruler(); - ImGui::Separator(); - ImGui::Text("%s Model", icon_cell); - { - with_indent indent{}; - int tmp = state.renderer.n_faces; - if (ImGui::DragInt("Frustrum Resolution", &tmp, 1.0f, 8, 64, "%d", ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_AlwaysClamp)) { - state.renderer.n_faces = tmp; - state.renderer.load_geometry(state.builder.morph); - for (const auto& rg: state.regions) state.update_region(rg); - for (const auto& ls: state.locsets) state.update_locset(ls); - } + if (gui_menu_item("Reset##camera", icon_refresh)) { + state.view.offset = {0.0f, 0.0f}; + state.view.rotate = glm::mat4(1.0f); + state.view.target = {0.0f, 0.0f, 0.0f}; + state.renderer.cell.clear_color = {214.0f/255, 214.0f/255, 214.0f/255}; } - ImGui::Separator(); - if (gui_menu_item("Snapshot", icon_paint)) state.store_snapshot(); - ImGui::InputText("Output",& state.snapshot_path); - ImGui::EndPopup(); } - ImGui::EndChild(); + ImGui::Separator(); + if(gui_axes(state.renderer.ax)) state.renderer.make_ruler(); + ImGui::Separator(); + ImGui::Text("%s Model", icon_cell); + { + with_indent indent{}; + int tmp = state.renderer.n_faces; + if (ImGui::DragInt("Frustrum Resolution", &tmp, 1.0f, 8, 64, "%d", ImGuiSliderFlags_Logarithmic | ImGuiSliderFlags_AlwaysClamp)) { + state.renderer.n_faces = tmp; + state.renderer.load_geometry(state.builder.morph); + for (const auto& rg: state.regions) state.update_region(rg); + for (const auto& ls: state.locsets) state.update_locset(ls); + } + } + ImGui::Separator(); + if (gui_menu_item("Snapshot", icon_paint)) state.store_snapshot(); + ImGui::InputText("Output",& state.snapshot_path); + ImGui::EndPopup(); } - ImGui::End(); } - inline void gui_cell_info(gui_state& state) { - if (ImGui::Begin(fmt::format("{} Morphology##info", icon_branch).c_str())) { - if (state.object) { - auto& object = state.object.value(); - ImGui::BulletText("Segment %u", object.data.id); - { - with_indent indent; - auto - px = object.data.prox.x, dx = object.data.dist.x, lx = dx - px, - py = object.data.prox.y, dy = object.data.dist.y, ly = dy - py, - pz = object.data.prox.z, dz = object.data.dist.z, lz = dz - pz; - ImGui::BulletText("Extent (%.1f, %.1f, %.1f) -- (%.1f, %.1f, %.1f)", px, py, pz, dx, dy, dz); - ImGui::BulletText("Radii %g µm %g µm", object.data.prox.radius, object.data.dist.radius); - ImGui::BulletText("Length %g µm", std::sqrt(lx*lx + ly*ly + lz*lz)); - } - ImGui::BulletText("Branch %zu", object.branch); - { - with_indent indent; - ImGui::BulletText("Segments"); - auto count = 0ul; - for (const auto& [lo, hi]: *object.segment_ids) { - ImGui::SameLine(); - if (lo == hi) { - ImGui::Text("%zu", lo); - } else { - ImGui::Text("%zu-%zu", lo, hi); - } - count += hi - lo + 1; + inline void gui_cell(gui_state& state) { + if (ImGui::Begin("Cell")) { + if (ImGui::BeginChild("Cell Render")) { + auto size = ImGui::GetWindowSize(), win_pos = ImGui::GetWindowPos(); + auto& vs = state.view; + vs.size = to_glmvec(size); + state.renderer.render(vs, {mouse.x - win_pos.x, size.y + win_pos.y - mouse.y}); + ImGui::Image(static_cast(state.renderer.cell.tex), size, ImVec2(0, 1), ImVec2(1, 0)); + if (ImGui::IsItemHovered()) { + auto shft = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift); + auto ctrl = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl); + if (shft || ctrl) { + auto what = shft ? ImGuizmo::ROTATE : ImGuizmo::TRANSLATE; + glm::vec3 shift = {vs.offset.x/vs.size.x, vs.offset.y/vs.size.y, 0.0f}; + glm::mat4 V = glm::lookAt(vs.camera, vs.target/state.renderer.rescale + shift, vs.up); + glm::mat4 P = glm::perspective(glm::radians(vs.zoom), vs.size.x/vs.size.y, 0.1f, 100.0f); + ImGuizmo::SetDrawlist(); + ImGuizmo::SetRect(win_pos.x, win_pos.y, size.x, size.y); + ImGuizmo::Manipulate(glm::value_ptr(V), glm::value_ptr(P), what, ImGuizmo::LOCAL, glm::value_ptr(vs.rotate)); + } + else { + vs.zoom = std::clamp(vs.zoom + delta_zoom, 1.0f, 45.0f); } - ImGui::BulletText("Count %zu", count); } - } - ImGui::End(); + + // + static float t_last = 0.0; + float t_now = glfwGetTime(); + if (state.demo_mode) vs.rotate = glm::rotate(vs.rotate, state.auto_omega*(t_now - t_last), glm::vec3{0.0f, 1.0f, 0.0f}); + t_last = t_now; + + state.object = state.renderer.get_id(); + } // cell render + ImGui::EndChild(); + } // cell + ImGui::End(); + } + + inline void gui_morph_info(gui_state& state) { + auto& object = state.object.value(); + ImGui::BulletText("Segment %u", object.data.id); + { + with_indent indent; + auto + px = object.data.prox.x, dx = object.data.dist.x, lx = dx - px, + py = object.data.prox.y, dy = object.data.dist.y, ly = dy - py, + pz = object.data.prox.z, dz = object.data.dist.z, lz = dz - pz; + ImGui::BulletText("Extent (%.1f, %.1f, %.1f) -- (%.1f, %.1f, %.1f)", px, py, pz, dx, dy, dz); + ImGui::BulletText("Radii %g µm %g µm", object.data.prox.radius, object.data.dist.radius); + ImGui::BulletText("Length %g µm", std::sqrt(lx*lx + ly*ly + lz*lz)); } - if (ImGui::Begin(fmt::format("{} Locations##info", icon_location).c_str())) { - if (state.object) { - auto& object = state.object.value(); - ImGui::BulletText("IExprs"); - { - with_indent indent; - for (const auto& iex: state.iexpr_defs.items) { - if (iex.state == def_state::good) { - auto& info = iex.info; - const auto& [pv, dv] = info.values.at(object.data.id); - ImGui::BulletText("%s: %f -- %f", iex.name.c_str(), pv, dv); - } - } + ImGui::BulletText("Branch %zu", object.branch); + { + with_indent indent; + ImGui::BulletText("Segments"); + auto count = 0ul; + for (const auto& [lo, hi]: *object.segment_ids) { + ImGui::SameLine(); + if (lo == hi) { + ImGui::Text("%zu", lo); + } else { + ImGui::Text("%zu-%zu", lo, hi); } - ImGui::BulletText("Regions"); - { - with_indent indent; - for (const auto& region: state.segment_to_regions[object.data.id]) { - ImGui::ColorButton("", to_imvec(state.renderer.regions[region].color)); - ImGui::SameLine(); - ImGui::AlignTextToFramePadding(); - ImGui::Text("%s", state.region_defs[region].name.c_str()); - } + count += hi - lo + 1; + } + ImGui::BulletText("Count %zu", count); + } + } + + inline void gui_loc_info(gui_state& state) { + auto& object = state.object.value(); + ImGui::BulletText("IExprs"); + { + with_indent indent; + for (const auto& iex: state.iexpr_defs.items) { + if (iex.state == def_state::good) { + auto& info = iex.info; + const auto& [pv, dv] = info.values.at(object.data.id); + ImGui::BulletText("%s: %f -- %f", iex.name.c_str(), pv, dv); } } - ImGui::End(); + } + ImGui::BulletText("Regions"); + { + with_indent indent; + for (const auto& region: state.segment_to_regions[object.data.id]) { + ImGui::ColorButton("", to_imvec(state.renderer.regions[region].color)); + ImGui::SameLine(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", state.region_defs[region].name.c_str()); + } } } + inline void gui_cell_info(gui_state& state) { + if (ImGui::Begin(fmt::format("{} Morphology##info", icon_branch).c_str())) { + if (state.object) gui_morph_info(state); + } + ImGui::End(); + if (ImGui::Begin(fmt::format("{} Locations##info", icon_location).c_str())) { + if (state.object) gui_loc_info(state); + } + ImGui::End(); + } + template inline void gui_locdefs(const std::string& name, entity& ids, @@ -550,7 +557,6 @@ namespace { component_unique& renderables, event_queue& events, bool show_color=true) { - with_id guard{name}; auto from = -1, to = -1; auto open = gui_tree_add(name, [&](){ events.emplace_back(evt_add_locdef{}); }); @@ -635,7 +641,7 @@ namespace { ImGui::Separator(); gui_locdefs(fmt::format("{} Inhomogeneous", icon_iexpr), state.iexprs, state.iexpr_defs, state.renderer.iexprs, state.events, false); } - ImGui::End(); + ImGui::End(); // locations } inline void gui_ion_settings(gui_state& state) { @@ -655,7 +661,6 @@ namespace { } inline void gui_ion_defaults(gui_state& state) { - with_id guard{"ion-defaults"}; auto open = gui_tree_add(fmt::format("{} Default", icon_default), [&]() { state.add_ion(); }); if (open) { @@ -685,7 +690,6 @@ namespace { } inline void gui_parameters(gui_state& state) { - with_id id{"parameters"}; if (ImGui::Begin(fmt::format("{} Parameters", icon_list).c_str())) { if (gui_tree(fmt::format("{} Cable Cell Properties", icon_sliders))) { @@ -715,7 +719,7 @@ namespace { } ImGui::Separator(); gui_mechanisms(state); - } + } // parameter ImGui::End(); } @@ -736,8 +740,11 @@ namespace { } } } - std::sort(state_vars.begin(), state_vars.end()); - std::unique(state_vars.begin(), state_vars.end()); + { + std::sort(state_vars.begin(), state_vars.end()); + auto last = std::unique(state_vars.begin(), state_vars.end()); + state_vars.erase(last, state_vars.end()); + } for (const auto& locset: state.locsets) { with_id id{locset}; @@ -775,13 +782,11 @@ namespace { inline void gui_debug(bool& open) { ImGui::ShowMetricsWindow(&open); } inline void gui_style(bool& open) { - if (ImGui::Begin("Style", &open)) ImGui::ShowStyleEditor(); ImGui::End(); } inline void gui_about(bool& open) { - if (ImGui::Begin("About", &open)) { ImGui::Text("Version: %s", gui_git_commit); ImGui::Text("Webpage: %s", gui_web_page); @@ -790,15 +795,12 @@ namespace { ImGui::End(); } - inline void gui_demo(bool& open) { - if (ImGui::Begin("Demo", &open)) ImGui::ShowDemoWindow(); ImGui::End(); } inline void gui_stimuli(gui_state& state) { - std::vector values(state.sim.until/state.sim.dt, 0.0f); if (gui_tree(fmt::format("{} Stimuli", icon_stimulus))) { for (const auto& locset: state.locsets) { @@ -820,7 +822,6 @@ namespace { if (ImGui::Begin(fmt::format("{} Simulation", icon_sim).c_str())) { ImGui::Separator(); gui_sim(state.sim); - ImGui::Separator(); gui_cv_policy(state.cv_policy_def, state.renderer.cv_boundaries, state.events); ImGui::Separator(); @@ -850,20 +851,22 @@ namespace { i_clamp.frequency = item.frequency; i_clamp.phase = item.phase; std::sort(item.envelope.begin(), item.envelope.end()); - for (const auto& [t, i]: item.envelope) i_clamp.envelope.emplace_back(arb::i_clamp::envelope_point{t, i}); + for (const auto& [t, i]: item.envelope) { + i_clamp.envelope.emplace_back(arb::i_clamp::envelope_point{t * U::ms , i * U::nA }); + } decor.place(locset, i_clamp, item.tag); } for (const auto child: state.detectors.get_children(id)) { auto item = state.detectors[child]; - decor.place(locset, arb::threshold_detector{item.threshold}, item.tag); + decor.place(locset, arb::threshold_detector{item.threshold * U::mV}, item.tag); } } auto param = state.parameter_defaults; - if (param.RL) decor.set_default(arb::axial_resistivity{param.RL.value()}); - if (param.Cm) decor.set_default(arb::membrane_capacitance{param.Cm.value()}); - if (param.TK) decor.set_default(arb::temperature_K{param.TK.value()}); - if (param.Vm) decor.set_default(arb::init_membrane_potential{param.Vm.value()}); + if (param.RL) decor.set_default(arb::axial_resistivity{param.RL.value() * U::Ohm * U::cm}); + if (param.Cm) decor.set_default(arb::membrane_capacitance{param.Cm.value() * U::F / U::m2}); + if (param.TK) decor.set_default(arb::temperature{param.TK.value() * U::Kelvin }); + if (param.Vm) decor.set_default(arb::init_membrane_potential{param.Vm.value() * U::mV}); for (const auto& ion: state.ions) { const auto& data = state.ion_defaults[ion]; @@ -876,13 +879,13 @@ namespace { if (state.presets.ion_data.contains(name)) { auto p = state.presets.ion_data.at(name); - decor.set_default(arb::init_int_concentration{name, data.Xi.value_or(p.init_int_concentration.value())}); - decor.set_default(arb::init_ext_concentration{name, data.Xo.value_or(p.init_ext_concentration.value())}); - decor.set_default(arb::init_reversal_potential{name, data.Er.value_or(p.init_reversal_potential.value())}); + decor.set_default(arb::init_int_concentration{name, data.Xi.value_or(p.init_int_concentration.value()) * U::mM}); + decor.set_default(arb::init_ext_concentration{name, data.Xo.value_or(p.init_ext_concentration.value()) * U::mM}); + decor.set_default(arb::init_reversal_potential{name, data.Er.value_or(p.init_reversal_potential.value())* U::mV}); } else { - decor.set_default(arb::init_int_concentration{name, data.Xi.value()}); - decor.set_default(arb::init_ext_concentration{name, data.Xo.value()}); - decor.set_default(arb::init_reversal_potential{name, data.Er.value()}); + decor.set_default(arb::init_int_concentration{name, data.Xi.value() * U::mM}); + decor.set_default(arb::init_ext_concentration{name, data.Xo.value() * U::mM}); + decor.set_default(arb::init_reversal_potential{name, data.Er.value() * U::mV}); } } @@ -890,17 +893,17 @@ namespace { auto rg = state.region_defs[id]; if (!rg.data) continue; auto param = state.parameter_defs[id]; - if (param.RL) decor.paint(rg.data.value(), arb::axial_resistivity{param.RL.value()}); - if (param.Cm) decor.paint(rg.data.value(), arb::membrane_capacitance{param.Cm.value()}); - if (param.TK) decor.paint(rg.data.value(), arb::temperature_K{param.TK.value()}); - if (param.Vm) decor.paint(rg.data.value(), arb::init_membrane_potential{param.Vm.value()}); + if (param.RL) decor.paint(rg.data.value(), arb::axial_resistivity{param.RL.value() * U::Ohm * U::cm}); + if (param.Cm) decor.paint(rg.data.value(), arb::membrane_capacitance{param.Cm.value() * U::F / U::m2}); + if (param.TK) decor.paint(rg.data.value(), arb::temperature{param.TK.value() * U::Kelvin}); + if (param.Vm) decor.paint(rg.data.value(), arb::init_membrane_potential{param.Vm.value() * U::mV}); for (const auto& ion: state.ions) { const auto& data = state.ion_par_defs[{id, ion}]; const auto& name = state.ion_defs[ion].name; - if (data.Xi) decor.paint(rg.data.value(), arb::init_int_concentration{name, data.Xi.value()}); - if (data.Xo) decor.paint(rg.data.value(), arb::init_ext_concentration{name, data.Xo.value()}); - if (data.Er) decor.paint(rg.data.value(), arb::init_reversal_potential{name, data.Er.value()}); + if (data.Xi) decor.paint(rg.data.value(), arb::init_int_concentration{name, data.Xi.value() * U::mM}); + if (data.Xo) decor.paint(rg.data.value(), arb::init_ext_concentration{name, data.Xo.value() * U::mM}); + if (data.Er) decor.paint(rg.data.value(), arb::init_reversal_potential{name, data.Er.value() * U::mV}); } for (const auto child: state.mechanisms.get_children(id)) { @@ -932,53 +935,60 @@ namespace { return {state.builder.morph, decor, state.builder.labels}; } - void gui_traces(gui_state& state) { - static std::optional to_plot; - if (ImGui::Begin("Traces")) { - if (ImGui::BeginChild("TracePlot", {-180.0f, 0.0f})) { - if (to_plot) { - auto probe = to_plot.value(); - auto trace = state.sim.traces.at(probe); - const auto& [lo, hi] = std::accumulate(trace.values.begin(), - trace.values.end(), - std::make_pair(std::numeric_limits::max(), std::numeric_limits::min()), - [] (const auto& p, auto x) -> std::pair { return { std::min(x, p.first), std::max(x, p.second)};}); - auto probe_def = state.probes[probe]; - auto var = fmt::format("{} {}", probe_def.kind, probe_def.variable); - - if (ImPlot::BeginPlot(fmt::format("Probe {} @ branch {} ({})", probe.value, trace.branch, trace.location).c_str(), - ImVec2(-1, -20))) { - ImPlot::SetupAxes("Time (t/ms)", var.c_str()); - ImPlot::SetupAxesLimits(0, state.sim.until, lo, hi); - ImPlot::SetupFinish(); - ImPlot::PlotLine(var.c_str(), trace.times.data(), trace.values.data(), trace.values.size()); - ImPlot::EndPlot(); - } - } else { - if (ImPlot::BeginPlot("Please select a probe below")) { - ImPlot::EndPlot(); - } + inline void gui_plot(gui_state& state, std::optional to_plot) { + if (ImGui::BeginChild("TracePlot", {-180.0f, 0.0f})) { + if (to_plot) { + auto probe = to_plot.value(); + auto trace = state.sim.traces.at(probe.value); + const auto& [lo, hi] = std::accumulate(trace.values.begin(), + trace.values.end(), + std::make_pair(std::numeric_limits::max(), std::numeric_limits::min()), + [] (const auto& p, auto x) -> std::pair { return { std::min(x, p.first), std::max(x, p.second)};}); + auto probe_def = state.probes[probe]; + auto var = fmt::format("{} {}", probe_def.kind, probe_def.variable); + + if (ImPlot::BeginPlot(fmt::format("Probe {} @ branch {} ({})", probe.value, trace.branch, trace.location).c_str(), + ImVec2(-1, -20))) { + ImPlot::SetupAxes("Time (t/ms)", var.c_str()); + ImPlot::SetupAxesLimits(0, state.sim.until, lo, hi); + ImPlot::SetupFinish(); + ImPlot::PlotLine(var.c_str(), trace.times.data(), trace.values.data(), trace.values.size()); + ImPlot::EndPlot(); + } + } else { + if (ImPlot::BeginPlot("Please select a probe below")) { + ImPlot::EndPlot(); } } - ImGui::EndChild(); - ImGui::SameLine(); - if (ImGui::BeginChild("TraceSelect", {150.0f, 0.0f})) { - for (const auto& locset: state.locsets) { - with_id id{locset}; - auto locset_def = state.locset_defs[locset]; - if (gui_tree(fmt::format("{} {}", icon_locset, locset_def.name))) { - for (const auto& probe: state.probes.get_children(locset)) { - const auto& data = state.probes[probe]; - if(ImGui::RadioButton(fmt::format("{} {}: {} {}", icon_probe, probe.value, data.kind, data.variable).c_str(), - to_plot && (to_plot.value() == probe))) { - to_plot = probe; - } + } + ImGui::EndChild(); + } + + inline void gui_trace_select(gui_state& state, std::optional to_plot) { + if (ImGui::BeginChild("TraceSelect", {150.0f, 0.0f})) { + for (const auto& locset: state.locsets) { + with_id id{locset}; + auto locset_def = state.locset_defs[locset]; + if (gui_tree(fmt::format("{} {}", icon_locset, locset_def.name))) { + for (const auto& probe: state.probes.get_children(locset)) { + const auto& data = state.probes[probe]; + if(ImGui::RadioButton(fmt::format("{} {}: {} {}", icon_probe, probe.value, data.kind, data.variable).c_str(), + to_plot && (to_plot.value() == probe))) { + to_plot = probe; } - ImGui::TreePop(); } + ImGui::TreePop(); } } - ImGui::EndChild(); + } + ImGui::EndChild(); + } + + void gui_traces(gui_state& state) { + static std::optional to_plot; + if (ImGui::Begin("Traces")) { + gui_plot(state, to_plot); + ImGui::SameLine(); } ImGui::End(); } @@ -1012,9 +1022,9 @@ void gui_state::deserialize(const std::filesystem::path& fn) { struct ls_visitor { gui_state* state; id_type locset; - std::string tag; + arb::hash_type tag; - ls_visitor(gui_state* s, const arb::locset& l, const std::string& t): state{s}, tag{t} { + ls_visitor(gui_state* s, const arb::locset& l, const arb::hash_type& t): state{s}, tag{t} { auto ls = to_string(l); auto res = std::find_if(state->locsets.begin(), state->locsets.end(), [&](const auto& id) { @@ -1082,7 +1092,7 @@ void gui_state::deserialize(const std::filesystem::path& fn) { void operator()(const arb::init_membrane_potential& t) { state->parameter_defs[region].Vm = t.value; } void operator()(const arb::axial_resistivity& t) { state->parameter_defs[region].RL = t.value; } - void operator()(const arb::temperature_K& t) { state->parameter_defs[region].TK = t.value; } + void operator()(const arb::temperature& t) { state->parameter_defs[region].TK = t.value; } void operator()(const arb::membrane_capacitance& t) { state->parameter_defs[region].Cm = t.value; } void operator()(const arb::init_int_concentration& t) { auto ion = std::find_if(state->ions.begin(), state->ions.end(), @@ -1117,20 +1127,7 @@ void gui_state::deserialize(const std::filesystem::path& fn) { } } void operator()(const arb::density& d) { make_density(d); } - }; - - struct df_visitor { - gui_state* state; - void operator()(const arb::init_membrane_potential& t) { log_error("Cannot handle this"); } - void operator()(const arb::axial_resistivity& t) { log_error("Cannot handle this"); } - void operator()(const arb::temperature_K& t) { log_error("Cannot handle this"); } - void operator()(const arb::membrane_capacitance& t) { log_error("Cannot handle this"); } - void operator()(const arb::init_int_concentration& t) { log_error("Cannot handle this"); } - void operator()(const arb::init_ext_concentration& t) { log_error("Cannot handle this"); } - void operator()(const arb::init_reversal_potential& t) { log_error("Cannot handle this"); } - void operator()(const arb::ion_reversal_potential_method& t) { log_error("Cannot handle this"); } - void operator()(const arb::ion_diffusivity& t) { log_error("Cannot handle this"); } - void operator()(const arb::cv_policy& t) { log_error("Cannot handle this"); } + void operator()(const arb::voltage_process& d) { log_error("Cannot handle this"); } }; struct acc_visitor { @@ -1425,7 +1422,7 @@ void gui_state::run_simulation() { if (parameter_defaults.Vm) prop.default_parameters.init_membrane_potential = parameter_defaults.Vm; auto cat = arb::mechanism_catalogue{}; - for (const auto& [k, v]: catalogues) cat.import(v, k + "::"); + for (const auto& [k, v]: catalogues) cat.extend(v, k + "::"); prop.catalogue = cat; for (const auto& ion: ions) { @@ -1437,15 +1434,15 @@ void gui_state::run_simulation() { auto p = presets.ion_data.at(name); prop.add_ion(name, data.charge, - def.Xi.value_or(p.init_int_concentration.value()), - def.Xo.value_or(p.init_ext_concentration.value()), - def.Er.value_or(p.init_reversal_potential.value())); + def.Xi.value_or(p.init_int_concentration.value()) * U::mM, + def.Xo.value_or(p.init_ext_concentration.value()) * U::mM, + def.Er.value_or(p.init_reversal_potential.value())* U::mV); } else { prop.add_ion(name, data.charge, - def.Xi.value(), - def.Xo.value(), - def.Er.value()); + def.Xi.value() * U::mM, + def.Xo.value() * U::mM, + def.Er.value() * U::mV); } } auto rec = make_recipe(prop, cell); @@ -1455,39 +1452,48 @@ void gui_state::run_simulation() { const auto& where = locset_defs[ls]; if (!where.data) continue; auto loc = where.data.value(); + // TODO this is quite crude... + auto tag = std::to_string(pb.value); if (data.kind == "Voltage") { - rec.probes.emplace_back(arb::cable_probe_membrane_voltage{loc}, pb.value); + rec.probes.emplace_back(arb::cable_probe_membrane_voltage{loc}, tag); } else if (data.kind == "Axial Current") { - rec.probes.emplace_back(arb::cable_probe_axial_current{loc}, pb.value); + rec.probes.emplace_back(arb::cable_probe_axial_current{loc}, tag); } else if (data.kind == "Membrane Current") { - rec.probes.emplace_back(arb::cable_probe_total_ion_current_density{loc}, pb.value); + rec.probes.emplace_back(arb::cable_probe_total_ion_current_density{loc}, tag); } // TODO Finish } } // Make simulation - auto sm = arb::simulation(rec); + auto sm = arb::simulation(rec); sim.traces.clear(); + sim.tag_to_id.clear(); sm.add_sampler(arb::all_probes, - arb::regular_schedule(this->sim.dt), - [&](arb::probe_metadata pm, std::size_t n, const arb::sample_record* samples) { - auto loc = arb::util::any_cast(pm.meta); - trace t{(size_t)pm.tag, pm.index, loc->pos, loc->branch, {}, {}}; + arb::regular_schedule(this->sim.dt * U::ms), + [&](const arb::probe_metadata pm, std::size_t n, const arb::sample_record* samples) { + auto loc = arb::util::any_cast(pm.meta); + auto tag = pm.id.tag; + if (sim.tag_to_id.count(tag) == 0) { + id_type id = {sim.traces.size()}; + sim.tag_to_id[tag] = id; + sim.traces.emplace_back(tag, id, pm.index, loc->pos, loc->branch); + } + auto id = sim.tag_to_id[tag]; + auto& t = sim.traces.at(id.value); for (std::size_t i = 0; i(samples[i].data); t.times.push_back(samples[i].time); t.values.push_back(*value); } - sim.traces[t.id] = t; - }, - arb::sampling_policy::exact); + }); try { - sm.run(sim.until, sim.dt); + sm.run(sim.until * U::ms, sim.dt * U::ms); } catch (...) { ImGui::OpenPopup("Error"); } if (ImGui::BeginPopupModal("Error")) { ImGui::Text("Arbor failed to run."); if (ImGui::Button("OK")) ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); } } diff --git a/src/loader.cpp b/src/loader.cpp index d25a97f..fc38a62 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include @@ -14,6 +13,7 @@ namespace io { loaded_morphology load_swc(const std::filesystem::path &fn, std::function &)> swc_to_morph) { + return { swc_to_morph(arborio::parse_swc(slurp(fn)).records()), {{"soma", "(tag 1)"}, {"axon", "(tag 2)"}, @@ -22,8 +22,23 @@ loaded_morphology load_swc(const std::filesystem::path &fn, {}}; } -loaded_morphology load_neuron_swc(const std::filesystem::path &fn) { return load_swc(fn, arborio::load_swc_neuron); } -loaded_morphology load_arbor_swc(const std::filesystem::path &fn) { return load_swc(fn, arborio::load_swc_arbor); } +loaded_morphology load_neuron_swc(const std::filesystem::path &fn) { + auto loaded = arborio::load_swc_neuron(fn); + loaded_morphology res{.morph=loaded.morphology}; + for (const auto& [k, v]: loaded.labels.regions()) res.regions.emplace_back(k, to_string(v)); + for (const auto& [k, v]: loaded.labels.locsets()) res.locsets.emplace_back(k, to_string(v)); + log_info("Loaded SWC (Neuron) {} branches {} ", loaded.segment_tree.size(), loaded.morphology.num_branches()); + return res; +} + +loaded_morphology load_arbor_swc(const std::filesystem::path &fn) { + auto loaded = arborio::load_swc_arbor(fn); + loaded_morphology res{.morph=loaded.morphology}; + for (const auto& [k, v]: loaded.labels.regions()) res.regions.emplace_back(k, to_string(v)); + for (const auto& [k, v]: loaded.labels.locsets()) res.locsets.emplace_back(k, to_string(v)); + log_info("Loaded SWC (Arbor) {} branches {} ", loaded.segment_tree.size(), loaded.morphology.num_branches()); + return res; +} loaded_morphology load_neuroml_morph(const std::filesystem::path &fn) { arborio::neuroml nml(slurp(fn)); @@ -33,8 +48,8 @@ loaded_morphology load_neuroml_morph(const std::filesystem::path &fn) { if (!morph_data) log_error("Invalid morphology id {} in NML file."); auto morph = morph_data.value(); loaded_morphology result{.morph=morph.morphology}; - for (const auto& [k, v]: morph.groups.regions()) result.regions.emplace_back(k, to_string(v)); - for (const auto& [k, v]: morph.groups.locsets()) result.locsets.emplace_back(k, to_string(v)); + for (const auto& [k, v]: morph.labels.regions()) result.regions.emplace_back(k, to_string(v)); + for (const auto& [k, v]: morph.labels.locsets()) result.locsets.emplace_back(k, to_string(v)); return result; } @@ -46,8 +61,8 @@ loaded_morphology load_neuroml_cell(const std::filesystem::path &fn) { if (!morph_data) log_error("Invalid cell id {} in NML file."); auto morph = morph_data.value(); loaded_morphology result{.morph=morph.morphology}; - for (const auto& [k, v]: morph.groups.regions()) result.regions.emplace_back(k, to_string(v)); - for (const auto& [k, v]: morph.groups.locsets()) result.locsets.emplace_back(k, to_string(v)); + for (const auto& [k, v]: morph.labels.regions()) result.regions.emplace_back(k, to_string(v)); + for (const auto& [k, v]: morph.labels.locsets()) result.locsets.emplace_back(k, to_string(v)); return result; } diff --git a/src/loader.hpp b/src/loader.hpp index 2784cd8..3384804 100644 --- a/src/loader.hpp +++ b/src/loader.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/src/recipe.hpp b/src/recipe.hpp index b2455f2..6d8af70 100644 --- a/src/recipe.hpp +++ b/src/recipe.hpp @@ -22,7 +22,7 @@ struct recipe: arb::recipe { std::any get_global_properties(arb::cell_kind) const override { return properties; } }; - +inline recipe make_recipe(const arb::cable_cell_global_properties& properties, const arb::cable_cell& cell) { recipe result; diff --git a/src/simulation.hpp b/src/simulation.hpp index 9ce4330..718e2e5 100644 --- a/src/simulation.hpp +++ b/src/simulation.hpp @@ -5,9 +5,9 @@ #include #include "id.hpp" -#include "utils.hpp" struct trace { + std::string tag; id_type id; size_t index; double location; @@ -15,6 +15,10 @@ struct trace { bool show = true; std::vector times; std::vector values; + + trace(const std::string t, const id_type i, size_t x, const double l, const size_t b): + tag{std::move(t)}, id{i}, index{x}, location{l}, branch{b} + {} }; @@ -25,7 +29,8 @@ struct simulation { bool should_run = false; bool show_trace = false; - std::unordered_map traces; + std::unordered_map tag_to_id; + std::vector traces; }; void gui_sim(simulation&); diff --git a/src/utils.cpp b/src/utils.cpp index 634d138..8b6f3fc 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -29,7 +29,7 @@ std::filesystem::path get_resource_path(const std::filesystem::path& fn) { } void log_init() { - spdlog::set_level(spdlog::level::warn); + spdlog::set_level(spdlog::level::info); } diff --git a/src/window.cpp b/src/window.cpp index 797d818..0bc73a2 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -59,14 +59,8 @@ Window::Window() { ImPlot::CreateContext(); ImGuiIO& io = ImGui::GetIO(); - // Enable keyboard navigation + // Enable keyboard navigation and docking io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - // Update key mapping - { - #define MAP_KEY(NAV_NO, KEY_NO) { if (io.KeysDown[KEY_NO]) io.NavInputs[NAV_NO] = 1.0f; } - MAP_KEY(ImGuiNavInput_Activate, GLFW_KEY_SPACE); - #undef MAP_KEY - } io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; if (!(io.ConfigFlags & ImGuiConfigFlags_DockingEnable)) log_error("ImGui docking disabled");