diff --git a/docs/docs/tutorial/plastic_beads.md b/docs/docs/tutorial/plastic_beads.md index 6adc21b8..1b34af67 100644 --- a/docs/docs/tutorial/plastic_beads.md +++ b/docs/docs/tutorial/plastic_beads.md @@ -29,17 +29,17 @@ foamstream-tomo --datafile --pdata tomo --p Open another terminal and run: ```sh -recastx-recon --rows 400 --cols 130 --angles 200 --minx -256 --maxx 256 --miny -256 --maxy 256 --preview-size 512 +recastx-recon --rows 400 --cols 130 --angles 200 --minx -256 --maxx 256 --miny -256 --maxy 256 --volume-size 512 ``` You can find the shapes of the DARK, FLAT and PROJECTION data from the output of foamstream. !!! note - The `preview-size`, which defines the resolution of the "low-resolution" + The `volume-size`, which defines the resolution of the "low-resolution" volume, is set to 512 in this case. As a result, the resolution of the reconstructed volume is indeed high. It should be noted that it thus takes longer time to perform the reconstruction and send the reconstructed volume - from the server to the client. Therefore, the default value of `preview-size` + from the server to the client. Therefore, the default value of `volume-size` is set to 128 and is recommended to use in [dynamic tomography](./dynamic_tomography.md). ### Starting the GUI client diff --git a/docs/docs/user_guide/gui.md b/docs/docs/user_guide/gui.md index 73f69b61..32a56289 100644 --- a/docs/docs/user_guide/gui.md +++ b/docs/docs/user_guide/gui.md @@ -25,12 +25,17 @@ - *Auto Levels*: Check to enable automatically setting color levels of the displayed objects. - *Min*: Minimum colormap value. - *Max*: Maximum colormap value. -- *Slices* - - *Y-Z*: Toggle the visibility of the slice in the y-z plane. - - *X-Z*: Toggle the visibility of the slice in the x-z plane. - - *X-Y*: Toggle the visibility of the slice in the x-y plane. - - *Reset*: Click to reset the positions and orientations of all the slices. -- *Show volume*: Check to display the low-resolution 3D volume. +- *Slice 1/2/3* + - *2D*: Check to display the 2D high-resolution slice. + - *3D*: Check to display the 3D high-resolution slab (TBD). + - *Disable*: Check to hide the slice. +- *Reset all slices*: Click to reset the positions and orientations of all the slices. +- *Show slice histograms*: Check to display the pixel value histogram for each slice. +- *Volume* + - *Preview*: Check to use the 3D reconstructed volume as preview. + - *Show*: Check to display the 3D reconstructed volume. + - *Disable*: Check to disable the reconstruction of the 3D volume. As a result, + there will be no preview when moving a slice. - *Alpha*: Opacity of the low-resolution 3D volume. ## Manipulating 3D model with mouse and keyboard diff --git a/gui/include/graphics/items/recon_item.hpp b/gui/include/graphics/items/recon_item.hpp index 4c07c224..4523b1cf 100644 --- a/gui/include/graphics/items/recon_item.hpp +++ b/gui/include/graphics/items/recon_item.hpp @@ -33,12 +33,10 @@ class Wireframe; class ReconItem : public GraphicsItem, public GraphicsGLItem, public GraphicsDataItem { enum class DragType : int { none, rotator, translator}; + + enum SlicePolicy { SHOW2D_SLI = 0, SHOW3D_SLI = 1, DISABLE_SLI = 2}; - enum { - Slice_YZ = 0, - Slice_XZ = 1, - Slice_XY = 2 - }; + enum VolumePolicy { PREVIEW_VOL = 0, SHOW_VOL = 1, DISABLE_VOL = 2}; class DragMachine { @@ -86,9 +84,7 @@ class ReconItem : public GraphicsItem, public GraphicsGLItem, public GraphicsDat friend class SliceRotator; std::vector>> slices_; - std::unique_ptr volume_; - - std::unique_ptr wireframe_; + std::array slice_policies_ { SHOW2D_SLI, SHOW2D_SLI, SHOW2D_SLI }; GLuint rotation_axis_vao_; GLuint rotation_axis_vbo_; @@ -103,9 +99,12 @@ class ReconItem : public GraphicsItem, public GraphicsGLItem, public GraphicsDat float min_val_; float max_val_; - bool show_volume_ = false; + std::unique_ptr volume_; + int volume_policy_ = PREVIEW_VOL; float volume_alpha_ = 1.f; + std::unique_ptr wireframe_; + glm::mat4 matrix_; bool show_statistics_ = true; @@ -119,8 +118,15 @@ class ReconItem : public GraphicsItem, public GraphicsGLItem, public GraphicsDat void initSlices(); + template + void renderImSliceControl(const char* header); + + void renderImVolumeControl(); + bool updateServerSliceParams(); + bool updateServerVolumeParams(); + void updateHoveringSlice(float x, float y); std::vector sortedSlices() const; diff --git a/gui/include/graphics/slice.hpp b/gui/include/graphics/slice.hpp index 09a43b4c..76acd87d 100644 --- a/gui/include/graphics/slice.hpp +++ b/gui/include/graphics/slice.hpp @@ -30,8 +30,6 @@ class Slice { using SizeType = std::array; using Orient4Type = glm::mat4; - friend class ReconItem; - private: int id_ = -1; @@ -73,7 +71,7 @@ class Slice { [[nodiscard]] bool visible() const { return visible_; } - void show(bool visible) { visible_ = visible; } + void setVisible(bool visible) { visible_ = visible; } [[nodiscard]] bool transparent() const; diff --git a/gui/include/graphics/style.hpp b/gui/include/graphics/style.hpp index dcc1321e..7b580816 100644 --- a/gui/include/graphics/style.hpp +++ b/gui/include/graphics/style.hpp @@ -33,12 +33,13 @@ struct Style { colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.05f); colors[ImGuiCol_Border] = ImVec4(0.43f, 0.43f, 0.50f, 0.00f); colors[ImGuiCol_FrameBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.60f); + colors[ImGuiCol_Header] = ImVec4(0.00f, 0.00f, 0.00f, 0.20f); colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.40f); colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); colors[ImGuiCol_Button] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f); colors[ImGuiCol_Separator] = ImVec4(0.90f, 0.90f, 0.90f, 0.60f); - + colors[ImGuiCol_SliderGrab] = ImVec4(0.40f, 0.40f, 0.40f, 0.80f); } }; diff --git a/gui/include/graphics/volume.hpp b/gui/include/graphics/volume.hpp index e6388845..ed955762 100644 --- a/gui/include/graphics/volume.hpp +++ b/gui/include/graphics/volume.hpp @@ -93,6 +93,8 @@ class Volume { void setData(DataType&& data, const SizeType& size); + void clearData(); + void render(const glm::mat4& view, const glm::mat4& projection, float min_v, diff --git a/gui/include/rpc_client.hpp b/gui/include/rpc_client.hpp index 816b69ef..05bd976d 100644 --- a/gui/include/rpc_client.hpp +++ b/gui/include/rpc_client.hpp @@ -90,6 +90,8 @@ class RpcClient { bool setSlice(uint64_t timestamp, const Orientation& orientation); + bool setVolume(bool required); + void start(); }; diff --git a/gui/include/utils.hpp b/gui/include/utils.hpp index 9b64226a..895a14af 100644 --- a/gui/include/utils.hpp +++ b/gui/include/utils.hpp @@ -40,11 +40,15 @@ std::tuple intersectionPoint(const glm::mat4& inv_matrix class FpsCounter { - int frames_ = 0; - double prev_; - double threshold_; - double fps_ = 0.; + int v_frames_ = 0; + double v_prev_; + double v_fps_ = 0.; + + int s_frames_ = 0; + double s_prev_; + double s_fps_ = 0.; + double threshold_; static constexpr double reset_interval_ = 5.; public: @@ -52,9 +56,11 @@ class FpsCounter { FpsCounter(); ~FpsCounter(); - void update(); + void countVolume(); + void countSlice(); - [[nodiscard]] double frameRate(); + [[nodiscard]] double volumeFrameRate(); + [[nodiscard]] double sliceFrameRate(); }; inline std::ostream& operator<<(std::ostream& stream, const glm::vec3& v) { diff --git a/gui/src/graphics/items/recon_item.cpp b/gui/src/graphics/items/recon_item.cpp index d2684b6f..3ee43389 100644 --- a/gui/src/graphics/items/recon_item.cpp +++ b/gui/src/graphics/items/recon_item.cpp @@ -69,7 +69,7 @@ void ReconItem::renderIm() { ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "RECONSTRUCTION"); auto& cmd = Colormap::data(); - if (ImGui::BeginCombo("Colormap##Widget", cmd.GetName(cm_.get()))) { + if (ImGui::BeginCombo("Colormap##RECON", cmd.GetName(cm_.get()))) { for (auto idx : Colormap::options()) { const char* name = cmd.GetName(idx); if (ImGui::Selectable(name, cm_.get() == idx)) { @@ -88,28 +88,16 @@ void ReconItem::renderIm() { ImGui::DragFloatRange2("Min / Max", &min_val_, &max_val_, step_size, std::numeric_limits::lowest(), // min() does not work std::numeric_limits::max()); - ImGui::AlignTextToFramePadding(); - ImGui::Text("Slices: "); - ImGui::SameLine(); - ImGui::Checkbox("Y-Z##RECON", &slices_[Slice_YZ].second->visible_); - ImGui::SameLine(); - ImGui::Checkbox("X-Z##RECON", &slices_[Slice_XZ].second->visible_); - ImGui::SameLine(); - ImGui::Checkbox("X-Y##RECON", &slices_[Slice_XY].second->visible_); - ImGui::SameLine(); - if(ImGui::Button("Reset")) { + + renderImSliceControl<0>("Slice 1##RECON"); + renderImSliceControl<1>("Slice 2##RECON"); + renderImSliceControl<2>("Slice 3##RECON"); + + if (ImGui::Button("Reset all slices")) { initSlices(); updateServerSliceParams(); } - - ImGui::Checkbox("Show volume", &show_volume_); ImGui::SameLine(); - ImGui::BeginDisabled(!show_volume_); - ImGui::PushItemWidth(-40); - ImGui::SliderFloat("Alpha", &volume_alpha_, 0.0f, 1.0f); - ImGui::PopItemWidth(); - ImGui::EndDisabled(); - ImGui::Checkbox("Show slice histograms", &show_statistics_); if (show_statistics_) { ImGui::SetNextWindowPos(st_win_pos_); @@ -136,9 +124,12 @@ void ReconItem::renderIm() { ImGui::End(); } + renderImVolumeControl(); + ImGui::Separator(); - scene_.setStatus("tomoUpdateFrameRate", fps_counter_.frameRate()); + scene_.setStatus("volumeUpdateFrameRate", fps_counter_.volumeFrameRate()); + scene_.setStatus("sliceUpdateFrameRate", fps_counter_.sliceFrameRate()); } void ReconItem::onFramebufferSizeChanged(int /* width */, int /* height */) {} @@ -164,7 +155,7 @@ void ReconItem::renderGl() { } volume_->unbind(); - if (show_volume_) { + if (volume_policy_ == SHOW_VOL) { volume_->render(view, projection, min_val, max_val_, volume_alpha_); } @@ -189,7 +180,7 @@ void ReconItem::renderGl() { } bool ReconItem::updateServerParams() { - return updateServerSliceParams(); + return updateServerSliceParams() || updateServerVolumeParams(); } void ReconItem::setSliceData(const std::string& data, @@ -228,13 +219,14 @@ bool ReconItem::consume(const DataType& packet) { if (data.has_slice()) { auto& slice = data.slice(); setSliceData(slice.data(), {slice.row_count(), slice.col_count()}, slice.timestamp()); + fps_counter_.countSlice(); return true; } - if (data.has_volume()) { + if (volume_policy_ != DISABLE_VOL && data.has_volume()) { auto& volume = data.volume(); setVolumeData(volume.data(), {volume.row_count(), volume.col_count(), volume.slice_count()}); - fps_counter_.update(); + fps_counter_.countVolume(); return true; } } @@ -315,19 +307,68 @@ void ReconItem::initSlices() { assert(slices_.size() == MAX_NUM_SLICES); // slice along axis 0 = x - slices_[Slice_YZ].second->setOrientation(glm::vec3(0.0f, -1.0f, -1.0f), - glm::vec3(0.0f, 2.0f, 0.0f), - glm::vec3(0.0f, 0.0f, 2.0f)); + slices_[0].second->setOrientation(glm::vec3( 0.0f, -1.0f, -1.0f), + glm::vec3( 0.0f, 2.0f, 0.0f), + glm::vec3( 0.0f, 0.0f, 2.0f)); // slice along axis 1 = y - slices_[Slice_XZ].second->setOrientation(glm::vec3(-1.0f, 0.0f, -1.0f), - glm::vec3(2.0f, 0.0f, 0.0f), - glm::vec3(0.0f, 0.0f, 2.0f)); + slices_[1].second->setOrientation(glm::vec3(-1.0f, 0.0f, -1.0f), + glm::vec3( 2.0f, 0.0f, 0.0f), + glm::vec3( 0.0f, 0.0f, 2.0f)); // slice along axis 2 = z - slices_[Slice_XY].second->setOrientation(glm::vec3(-1.0f, -1.0f, 0.0f), - glm::vec3(2.0f, 0.0f, 0.0f), - glm::vec3(0.0f, 2.0f, 0.0f)); + slices_[2].second->setOrientation(glm::vec3(-1.0f, -1.0f, 0.0f), + glm::vec3( 2.0f, 0.0f, 0.0f), + glm::vec3( 0.0f, 2.0f, 0.0f)); +} + +template +void ReconItem::renderImSliceControl(const char* header) { + + static const char* BTN_2D[] = {"2D##RECON_SLI_0", "2D##RECON_SLI_1", "2D##RECON_SLI_2"}; + static const char* BTN_3D[] = {"3D##RECON_SLI_0", "3D##RECON_SLI_1", "3D##RECON_SLI_2"}; + static const char* BTN_DISABLE[] = {"Disable##RECON_SLI_0", "Disable##RECON_SLI_1", "Disable##RECON_SLI_2"}; + + static const ImVec4 K_HEADER_COLOR = (ImVec4)ImColor(65, 145, 151); + ImGui::PushStyleColor(ImGuiCol_Header, K_HEADER_COLOR); + bool expand = ImGui::CollapsingHeader(header, ImGuiTreeNodeFlags_DefaultOpen); + ImGui::PopStyleColor(); + if (expand) { + bool cd = false; + + cd |= ImGui::RadioButton(BTN_2D[index], &slice_policies_[index], SHOW2D_SLI); + ImGui::SameLine(); + cd |= ImGui::RadioButton(BTN_3D[index], &slice_policies_[index], SHOW3D_SLI); + ImGui::SameLine(); + cd |= ImGui::RadioButton(BTN_DISABLE[index], &slice_policies_[index], DISABLE_SLI); + + if (cd) slices_[index].second->setVisible(slice_policies_[index] != DISABLE_SLI); + } +} + +void ReconItem::renderImVolumeControl() { + static const ImVec4 K_HEADER_COLOR = (ImVec4)ImColor(65, 145, 151); + ImGui::PushStyleColor(ImGuiCol_Header, K_HEADER_COLOR); + bool expand = ImGui::CollapsingHeader("Volume##RECON", ImGuiTreeNodeFlags_DefaultOpen); + ImGui::PopStyleColor(); + + if (expand) { + bool cd = false; + cd |= ImGui::RadioButton("Preview##RECON_VOL", &volume_policy_, PREVIEW_VOL); + ImGui::SameLine(); + cd |= ImGui::RadioButton("Show##RECON_VOL", &volume_policy_, SHOW_VOL); + ImGui::SameLine(); + bool disabled = ImGui::RadioButton("Disable##RECON_VOL", &volume_policy_, DISABLE_VOL); + cd |= disabled; + + if (cd) updateServerVolumeParams(); + if (disabled) { + volume_->clearData(); + maybeUpdateMinMaxValues(); + } + + ImGui::SliderFloat("Alpha##RECON_VOL", &volume_alpha_, 0.0f, 1.0f); + } } bool ReconItem::updateServerSliceParams() { @@ -339,6 +380,10 @@ bool ReconItem::updateServerSliceParams() { return false; } +bool ReconItem::updateServerVolumeParams() { + return scene_.client()->setVolume(volume_policy_ != DISABLE_VOL); +} + void ReconItem::updateHoveringSlice(float x, float y) { auto inv_matrix = glm::inverse(matrix_); int slice_id = -1; diff --git a/gui/src/graphics/items/statusbar_item.cpp b/gui/src/graphics/items/statusbar_item.cpp index 35da1d74..42601cf5 100644 --- a/gui/src/graphics/items/statusbar_item.cpp +++ b/gui/src/graphics/items/statusbar_item.cpp @@ -43,9 +43,10 @@ void StatusbarItem::renderIm() { auto& io = ImGui::GetIO(); ImGui::Text("GUI FPS: %.1f", io.Framerate); - ImGui::Text("Tomogram (update) FPS: %.1f", - std::any_cast(scene_.getStatus("tomoUpdateFrameRate"))); - + ImGui::Text("Volume (update) FPS: %.1f", + std::any_cast(scene_.getStatus("volumeUpdateFrameRate"))); + ImGui::Text("Slice (update) FPS: %.1f", + std::any_cast(scene_.getStatus("sliceUpdateFrameRate"))); ImGui::End(); } diff --git a/gui/src/graphics/volume.cpp b/gui/src/graphics/volume.cpp index cf898a6c..e1b7bcb3 100644 --- a/gui/src/graphics/volume.cpp +++ b/gui/src/graphics/volume.cpp @@ -159,6 +159,16 @@ void Volume::setData(DataType&& data, const SizeType& size) { static_cast(size_[2])); } +void Volume::clearData() { + std::fill(data_.begin(), data_.end(), 0.f); + min_max_vals_ = {0.f, 0.f}; + + texture_.setData(data_, + static_cast(size_[0]), + static_cast(size_[1]), + static_cast(size_[2])); +} + void Volume::render(const glm::mat4& view, const glm::mat4& projection, float min_v, diff --git a/gui/src/rpc_client.cpp b/gui/src/rpc_client.cpp index abfa9014..3e580fdc 100644 --- a/gui/src/rpc_client.cpp +++ b/gui/src/rpc_client.cpp @@ -99,6 +99,18 @@ bool RpcClient::setSlice(uint64_t timestamp, const Orientation& orientation) { return checkStatus(status); } +bool RpcClient::setVolume(bool required) { + rpc::Volume request; + request.set_required(required); + + google::protobuf::Empty reply; + + grpc::ClientContext context; + + grpc::Status status = reconstruction_stub_->SetVolume(&context, request, &reply); + return checkStatus(status); +} + void RpcClient::start() { if (streaming_) return; streaming_ = true; diff --git a/gui/src/utils.cpp b/gui/src/utils.cpp index d47db0b0..e419f6fb 100644 --- a/gui/src/utils.cpp +++ b/gui/src/utils.cpp @@ -67,23 +67,38 @@ std::tuple intersectionPoint(const glm::mat4& inv_matrix // class FpsCounter -FpsCounter::FpsCounter() : prev_(glfwGetTime()), threshold_(1.) {} +FpsCounter::FpsCounter() : v_prev_(glfwGetTime()), s_prev_(v_prev_), threshold_(1.) {} FpsCounter::~FpsCounter() = default; -void FpsCounter::update() { - ++frames_; +void FpsCounter::countVolume() { + ++v_frames_; double curr = glfwGetTime(); - if (curr - prev_ > threshold_) { - fps_ = frames_ / (curr - prev_); - prev_ = curr; - frames_ = 0; + if (curr - v_prev_ > threshold_) { + v_fps_ = v_frames_ / (curr - v_prev_); + v_prev_ = curr; + v_frames_ = 0; } } -[[nodiscard]] double FpsCounter::frameRate() { - if (glfwGetTime() - prev_ > FpsCounter::reset_interval_) fps_ = 0.; - return fps_; +void FpsCounter::countSlice() { + ++s_frames_; + double curr = glfwGetTime(); + if (curr - s_prev_ > threshold_) { + s_fps_ = s_frames_ / (curr - s_prev_); + s_prev_ = curr; + s_frames_ = 0; + } +} + +[[nodiscard]] double FpsCounter::volumeFrameRate() { + if (glfwGetTime() - v_prev_ > FpsCounter::reset_interval_) v_fps_ = 0.; + return v_fps_; +} + +[[nodiscard]] double FpsCounter::sliceFrameRate() { + if (glfwGetTime() - s_prev_ > FpsCounter::reset_interval_) s_fps_ = 0.; + return s_fps_; } } // namespace recastx::gui diff --git a/gui/tests/server/rpc_server.cpp b/gui/tests/server/rpc_server.cpp index ccb6df41..f3a5c0e6 100644 --- a/gui/tests/server/rpc_server.cpp +++ b/gui/tests/server/rpc_server.cpp @@ -95,6 +95,13 @@ grpc::Status ReconstructionService::SetSlice(grpc::ServerContext* context, return grpc::Status::OK; } +grpc::Status ReconstructionService::SetVolume(grpc::ServerContext* context, + const rpc::Volume* volume, + google::protobuf::Empty* ack) { + spdlog::info("Set volume required: {}", volume->required()); + return grpc::Status::OK; +} + grpc::Status ReconstructionService::GetReconData(grpc::ServerContext* context, const google::protobuf::Empty*, grpc::ServerWriter* writer) { diff --git a/gui/tests/server/rpc_server.hpp b/gui/tests/server/rpc_server.hpp index 54ea00f0..3853768f 100644 --- a/gui/tests/server/rpc_server.hpp +++ b/gui/tests/server/rpc_server.hpp @@ -125,6 +125,10 @@ class ProjectionService final : public rpc::Projection::Service { const rpc::Slice* slice, google::protobuf::Empty* ack) override; + grpc::Status SetVolume(grpc::ServerContext* context, + const rpc::Volume* volume, + google::protobuf::Empty* ack) override; + grpc::Status GetReconData(grpc::ServerContext* context, const google::protobuf::Empty*, grpc::ServerWriter* writer) override; diff --git a/protos/reconstruction.proto b/protos/reconstruction.proto index 05c4c098..03bcd148 100644 --- a/protos/reconstruction.proto +++ b/protos/reconstruction.proto @@ -6,6 +6,8 @@ import "google/protobuf/empty.proto"; service Reconstruction { rpc SetSlice (Slice) returns (google.protobuf.Empty) {} + rpc SetVolume (Volume) returns (google.protobuf.Empty) {} + rpc GetReconData (google.protobuf.Empty) returns (stream ReconData) {} } @@ -34,3 +36,7 @@ message Slice { uint64 timestamp = 1; repeated float orientation = 2; } + +message Volume { + bool required = 1; +} diff --git a/recon/include/recon/application.hpp b/recon/include/recon/application.hpp index 9bcc3c91..1ce2c154 100644 --- a/recon/include/recon/application.hpp +++ b/recon/include/recon/application.hpp @@ -90,7 +90,7 @@ class Application { std::unique_ptr proj_mediator_; std::unique_ptr slice_mediator_; - TripleTensorBuffer preview_buffer_; + TripleTensorBuffer volume_buffer_; std::optional paganin_cfg_; std::unique_ptr paganin_; @@ -101,7 +101,7 @@ class Application { ImageprocParams imgproc_params_; std::optional slice_size_; - std::optional preview_size_; + std::optional volume_size_; std::optional min_x_; std::optional max_x_; std::optional min_y_; @@ -110,7 +110,8 @@ class Application { std::optional max_z_; ProjectionGeometry proj_geom_; VolumeGeometry slice_geom_; - VolumeGeometry preview_geom_; + VolumeGeometry volume_geom_; + bool volume_required_ = true; ReconstructorFactory* recon_factory_; std::unique_ptr recon_; @@ -192,7 +193,7 @@ class Application { float pixel_width, float pixel_height, float src2origin, float origin2det, size_t num_angles); - void setReconGeometry(std::optional slice_size, std::optional preview_size, + void setReconGeometry(std::optional slice_size, std::optional volume_size, std::optional min_x, std::optional max_x, std::optional min_y, std::optional max_y, std::optional min_z, std::optional max_z); @@ -213,13 +214,17 @@ class Application { void setSlice(size_t timestamp, const Orientation& orientation); + void setVolume(bool required); + void setScanMode(rpc::ScanMode_Mode mode, uint32_t update_inverval); void onStateChanged(rpc::ServerState_State state); std::optional getProjectionData(int timeout); - std::optional getPreviewData(int timeout); + bool hasVolume() const { return volume_required_; } + + std::optional getVolumeData(int timeout); std::vector getSliceData(int timeout); diff --git a/recon/include/recon/buffer.hpp b/recon/include/recon/buffer.hpp index 7d63c376..bdf31eea 100644 --- a/recon/include/recon/buffer.hpp +++ b/recon/include/recon/buffer.hpp @@ -352,6 +352,8 @@ class MemoryBuffer : public BufferInterface> { bool fetch(int timeout) override; + bool isReady() const { return is_ready_; } + BufferType& ready() { return buffer_[map_.at(chunk_indices_.front())]; } const BufferType& ready() const { return buffer_[map_.at(chunk_indices_.front())]; } diff --git a/recon/include/recon/reconstructor.hpp b/recon/include/recon/reconstructor.hpp index 2ae90036..f117487c 100644 --- a/recon/include/recon/reconstructor.hpp +++ b/recon/include/recon/reconstructor.hpp @@ -103,7 +103,7 @@ class ParallelBeamReconstructor : public AstraReconstructor { void reconstructSlice(Orientation x, int buffer_idx, Tensor& buffer) override; - void reconstructPreview(int buffer_idx, Tensor& buffer) override; + void reconstructVolume(int buffer_idx, Tensor& buffer) override; }; @@ -126,7 +126,7 @@ class ConeBeamReconstructor : public AstraReconstructor { void reconstructSlice(Orientation x, int buffer_idx, Tensor& buffer) override; - void reconstructPreview(int buffer_idx, Tensor& buffer) override; + void reconstructVolume(int buffer_idx, Tensor& buffer) override; std::vector fdk_weights(); }; @@ -139,7 +139,7 @@ class AstraReconstructorFactory : public ReconstructorFactory { size_t row_count, ProjectionGeometry proj_geom, VolumeGeometry slice_geom, - VolumeGeometry preview_geom, + VolumeGeometry volume_geom, bool double_buffering) override; }; diff --git a/recon/include/recon/reconstructor_interface.hpp b/recon/include/recon/reconstructor_interface.hpp index d34fd7c6..da58c4e2 100644 --- a/recon/include/recon/reconstructor_interface.hpp +++ b/recon/include/recon/reconstructor_interface.hpp @@ -24,7 +24,7 @@ class Reconstructor { virtual void reconstructSlice(Orientation x, int buffer_idx, Tensor& buffer) = 0; - virtual void reconstructPreview(int buffer_idx, Tensor& buffer) = 0; + virtual void reconstructVolume(int buffer_idx, Tensor& buffer) = 0; virtual void uploadSinograms(int buffer_idx, const float* data, size_t n) = 0; }; @@ -39,7 +39,7 @@ class ReconstructorFactory { size_t row_count, ProjectionGeometry proj_geom, VolumeGeometry slice_geom, - VolumeGeometry preview_geom, + VolumeGeometry volume_geom, bool double_buffering) = 0; }; diff --git a/recon/include/recon/rpc_server.hpp b/recon/include/recon/rpc_server.hpp index 982bca0a..98907afa 100644 --- a/recon/include/recon/rpc_server.hpp +++ b/recon/include/recon/rpc_server.hpp @@ -91,6 +91,10 @@ class ReconstructionService final : public rpc::Reconstruction::Service { const rpc::Slice* slice, google::protobuf::Empty* ack) override; + grpc::Status SetVolume(grpc::ServerContext* context, + const rpc::Volume* volume, + google::protobuf::Empty* ack) override; + grpc::Status GetReconData(grpc::ServerContext* context, const google::protobuf::Empty*, grpc::ServerWriter* writer) override; diff --git a/recon/src/application.cpp b/recon/src/application.cpp index e74adbf6..93089346 100644 --- a/recon/src/application.cpp +++ b/recon/src/application.cpp @@ -59,12 +59,12 @@ void Application::setProjectionGeometry(BeamShape beam_shape, size_t col_count, src2origin, origin2det, defaultAngles(num_angles)}; } -void Application::setReconGeometry(std::optional slice_size, std::optional preview_size, +void Application::setReconGeometry(std::optional slice_size, std::optional volume_size, std::optional minx, std::optional maxx, std::optional miny, std::optional maxy, std::optional minz, std::optional maxz) { slice_size_ = slice_size; - preview_size_ = preview_size; + volume_size_ = volume_size; min_x_ = minx; max_x_ = maxx; min_y_ = miny; @@ -156,8 +156,13 @@ void Application::startReconstructing() { { std::unique_lock lck(gpu_mtx_); if (gpu_cv_.wait_for(lck, 10ms, [&] { return sino_uploaded_; })) { - spdlog::info("Reconstructing preview and slices ..."); - recon_->reconstructPreview(gpu_buffer_index_, preview_buffer_.back()); + if (volume_required_) { + spdlog::info("Reconstructing volume and slices ..."); + recon_->reconstructVolume(gpu_buffer_index_, volume_buffer_.back()); + } else { + spdlog::info("Reconstructing slices ..."); + } + } else { if (waitForSinoInitialization()) continue; slice_mediator_->reconOnDemand(recon_.get(), gpu_buffer_index_); @@ -169,10 +174,10 @@ void Application::startReconstructing() { sino_uploaded_ = false; } - spdlog::debug("Preview and slices reconstructed!"); + spdlog::debug("Volume and slices reconstructed!"); monitor_->countTomogram(); - preview_buffer_.prepare(); + volume_buffer_.prepare(); } }); @@ -271,6 +276,10 @@ void Application::setSlice(size_t timestamp, const Orientation& orientation) { slice_mediator_->update(timestamp, orientation); } +void Application::setVolume(bool required) { + volume_required_ = required; +} + void Application::setScanMode(rpc::ScanMode_Mode mode, uint32_t update_interval) { scan_mode_ = mode; scan_update_interval_ = update_interval; @@ -314,9 +323,9 @@ std::optional Application::getProjectionData(int timeout) { return std::nullopt; } -std::optional Application::getPreviewData(int timeout) { - if (preview_buffer_.fetch(timeout)) { - auto& data = preview_buffer_.front(); +std::optional Application::getVolumeData(int timeout) { + if (volume_buffer_.fetch(timeout)) { + auto& data = volume_buffer_.front(); auto [x, y, z] = data.shape(); return createVolumeDataPacket(data, x, y, z); } @@ -496,19 +505,19 @@ void Application::initReconstructor(size_t col_count, size_t row_count) { auto [min_z, max_z] = details::parseReconstructedVolumeBoundary(min_z_, max_z_, row_count); size_t s_size = slice_size_.value_or(expandDataSizeForGpu(col_count, 64)); - size_t p_size = preview_size_.value_or(128); + size_t p_size = volume_size_.value_or(128); float half_slice_height = 0.5f * (max_z - min_z) / p_size; float z0 = 0.5f * (max_z + min_z); slice_geom_ = {s_size, s_size, 1, min_x, max_x, min_y, max_y, z0 - half_slice_height, z0 + half_slice_height}; - preview_geom_ = {p_size, p_size, p_size, min_x, max_x, min_y, max_y, min_y, max_y}; + volume_geom_ = {p_size, p_size, p_size, min_x, max_x, min_y, max_y, min_y, max_y}; - preview_buffer_.resize({preview_geom_.col_count, preview_geom_.row_count, preview_geom_.slice_count}); + volume_buffer_.resize({volume_geom_.col_count, volume_geom_.row_count, volume_geom_.slice_count}); slice_mediator_->resize({slice_geom_.col_count, slice_geom_.row_count}); bool double_buffering = scan_mode_ == rpc::ScanMode_Mode_DISCRETE; recon_ = recon_factory_->create( - col_count, row_count, proj_geom_, slice_geom_, preview_geom_, double_buffering); + col_count, row_count, proj_geom_, slice_geom_, volume_geom_, double_buffering); } void Application::maybeInitFlatFieldBuffer(size_t row_count, size_t col_count) { diff --git a/recon/src/main.cpp b/recon/src/main.cpp index c0881d1d..abdb9ac8 100644 --- a/recon/src/main.cpp +++ b/recon/src/main.cpp @@ -113,8 +113,8 @@ int main(int argc, char** argv) { reconstruction_desc.add_options() ("slice-size", po::value(), "size of the square reconstructed slice in pixels. Default to detector columns.") - ("preview-size", po::value(), - "size of the cubic reconstructed volume for preview.") + ("volume-size", po::value(), + "size of the cubic reconstructed volume. Default to 128.") ("raw-buffer-size", po::value()->default_value(10), "maximum number of projection groups to be cached in the memory buffer") ("retrieve-phase", po::bool_switch(&retrieve_phase), @@ -185,8 +185,8 @@ int main(int argc, char** argv) { auto slice_size = opts["slice-size"].empty() ? std::nullopt : std::optional(opts["slice-size"].as()); - auto preview_size = opts["preview-size"].empty() - ? std::nullopt : std::optional(opts["preview-size"].as()); + auto volume_size = opts["volume-size"].empty() + ? std::nullopt : std::optional(opts["volume-size"].as()); auto raw_buffer_size = opts["raw-buffer-size"].as(); auto ramp_filter = opts["ramp-filter"].as(); @@ -221,7 +221,7 @@ int main(int argc, char** argv) { if (retrieve_phase) app.setPaganinParams(pixel_size, lambda, delta, beta, distance); app.setProjectionGeometry(cone_beam ? recastx::BeamShape::CONE : recastx::BeamShape::PARALELL, num_cols, num_rows, 1.0f, 1.0f, 0.0f, 0.0f, num_angles); - app.setReconGeometry(slice_size, preview_size, minx, maxx, miny, maxy, minz, maxz); + app.setReconGeometry(slice_size, volume_size, minx, maxx, miny, maxy, minz, maxz); app.spin(parseServerState(auto_acquiring, auto_processing)); diff --git a/recon/src/reconstructor.cpp b/recon/src/reconstructor.cpp index 40589d7d..280c2523 100644 --- a/recon/src/reconstructor.cpp +++ b/recon/src/reconstructor.cpp @@ -88,12 +88,12 @@ AstraReconstructor::AstraReconstructor(const ProjectionGeometry& p_geom, s_geom_ = makeVolumeGeometry(s_geom); s_mem_ = makeVolumeGeometryMemHandle(s_geom); s_data_ = std::make_unique(s_geom_.get(), s_mem_); - spdlog::info("- Slice volume geometry: {}", details::astraInfo(*s_geom_)); + spdlog::info("- Slice geometry: {}", details::astraInfo(*s_geom_)); v_geom_ = makeVolumeGeometry(v_geom); v_mem_ = makeVolumeGeometryMemHandle(v_geom); v_data_ = std::make_unique(v_geom_.get(), v_mem_); - spdlog::info("- Preview volume geometry: {}", details::astraInfo(*v_geom_)); + spdlog::info("- Volume geometry: {}", details::astraInfo(*v_geom_)); for (int i = 0; i < (double_buffering ? 2 : 1); ++i) { uploader_.emplace_back(p_geom.angles.size()); @@ -156,7 +156,7 @@ ParallelBeamReconstructor::ParallelBeamReconstructor(size_t col_count, } spdlog::info("- Slice projection geometry: {}", details::astraInfo(*s_p_geom_)); - spdlog::info("- Preview projection geometry: {}", details::astraInfo(*v_p_geom_)); + spdlog::info("- Volume projection geometry: {}", details::astraInfo(*v_p_geom_)); vectors_ = std::vector( s_p_geom_->getProjectionVectors(), s_p_geom_->getProjectionVectors() + angle_count); @@ -225,13 +225,13 @@ void ParallelBeamReconstructor::reconstructSlice(Orientation x, int buffer_idx, astraCUDA3d::copyFromGPUMemory(buffer.data(), s_mem_, pos); } -void ParallelBeamReconstructor::reconstructPreview(int buffer_idx, Tensor& buffer) { +void ParallelBeamReconstructor::reconstructVolume(int buffer_idx, Tensor& buffer) { #if (VERBOSITY >= 2) - ScopedTimer timer("Bench", "Reconstructing preview"); + ScopedTimer timer("Bench", "Reconstructing volume"); #endif - spdlog::debug("Reconstructing preview with buffer index: {}", buffer_idx); + spdlog::debug("Reconstructing volume with buffer index: {}", buffer_idx); p_data_[buffer_idx]->changeGeometry(v_p_geom_.get()); v_algo_[buffer_idx]->run(); @@ -278,7 +278,7 @@ ConeBeamReconstructor::ConeBeamReconstructor(size_t col_count, } spdlog::info("- Slice projection geometry: {}", details::astraInfo(*s_p_geom_)); - spdlog::info("- Preview projection geometry: {}", details::astraInfo(*v_p_geom_)); + spdlog::info("- Volume projection geometry: {}", details::astraInfo(*v_p_geom_)); vectors_ = std::vector( s_p_geom_->getProjectionVectors(), s_p_geom_->getProjectionVectors() + angle_count); @@ -343,7 +343,7 @@ void ConeBeamReconstructor::reconstructSlice(Orientation x, int buffer_idx, Tens astraCUDA3d::copyFromGPUMemory(buffer.data(), s_mem_, pos); } -void ConeBeamReconstructor::reconstructPreview(int buffer_idx, Tensor& buffer) { +void ConeBeamReconstructor::reconstructVolume(int buffer_idx, Tensor& buffer) { p_data_[buffer_idx]->changeGeometry(v_p_geom_.get()); v_algo_[buffer_idx]->run(); @@ -389,14 +389,14 @@ AstraReconstructorFactory::create(size_t col_count, size_t row_count, ProjectionGeometry proj_geom, VolumeGeometry slice_geom, - VolumeGeometry preview_geom, + VolumeGeometry volume_geom, bool double_buffering) { if (proj_geom.beam_shape == BeamShape::CONE) { return std::make_unique( - col_count, row_count, proj_geom, slice_geom, preview_geom, double_buffering); + col_count, row_count, proj_geom, slice_geom, volume_geom, double_buffering); } return std::make_unique( - col_count, row_count, proj_geom, slice_geom, preview_geom, double_buffering); + col_count, row_count, proj_geom, slice_geom, volume_geom, double_buffering); } } // namespace recastx::recon diff --git a/recon/src/rpc_server.cpp b/recon/src/rpc_server.cpp index a80883f8..ff8d5a49 100644 --- a/recon/src/rpc_server.cpp +++ b/recon/src/rpc_server.cpp @@ -80,18 +80,28 @@ grpc::Status ReconstructionService::SetSlice(grpc::ServerContext* context, return grpc::Status::OK; } +grpc::Status ReconstructionService::SetVolume(grpc::ServerContext* context, + const rpc::Volume* volume, + google::protobuf::Empty* ack) { + + app_->setVolume(volume->required()); + return grpc::Status::OK; +} + grpc::Status ReconstructionService::GetReconData(grpc::ServerContext* context, const google::protobuf::Empty*, grpc::ServerWriter* writer) { // - Do not block because slice request needs to be responsive // - If the number of the logical threads are more than the number of the physical threads, - // the preview_data could always have value. - auto preview_data = app_->getPreviewData(0); - if (preview_data) { + // the volume_data could always have value. + auto volume_data = app_->getVolumeData(0); + if (volume_data) { auto slice_data = app_->getSliceData(-1); - writer->Write(preview_data.value()); - spdlog::debug("Preview data sent"); + if (app_->hasVolume()) { + writer->Write(volume_data.value()); + spdlog::debug("Volume data sent"); + } for (const auto& item : slice_data) { writer->Write(item); diff --git a/recon/tests/test_application.cpp b/recon/tests/test_application.cpp index 4eb886c4..07a2e73d 100644 --- a/recon/tests/test_application.cpp +++ b/recon/tests/test_application.cpp @@ -72,7 +72,7 @@ class MockReconstructor: public Reconstructor { size_t upload_counter_; size_t slice_counter_; - size_t preview_counter_; + size_t volume_counter_; public: @@ -80,20 +80,20 @@ class MockReconstructor: public Reconstructor { size_t row_count, ProjectionGeometry proj_geom, VolumeGeometry slice_geom, - VolumeGeometry preview_geom, + VolumeGeometry volume_geom, bool double_buffering) - : upload_counter_(0), slice_counter_(0), preview_counter_(0) { + : upload_counter_(0), slice_counter_(0), volume_counter_(0) { } void reconstructSlice(Orientation x, int buffer_idx, Tensor& buffer) override { ++slice_counter_; }; - void reconstructPreview(int buffer_idx, Tensor& buffer) override { ++preview_counter_; }; + void reconstructVolume(int buffer_idx, Tensor& buffer) override { ++volume_counter_; }; void uploadSinograms(int buffer_idx, const float* data, size_t n) override { ++upload_counter_; }; - size_t num_uploads() const { return upload_counter_; } - size_t num_slices() const { return slice_counter_; } - size_t num_previews() const { return preview_counter_; } + size_t numUploads() const { return upload_counter_; } + size_t numSlices() const { return slice_counter_; } + size_t numVolumes() const { return volume_counter_; } }; class MockReconFactory: public ReconstructorFactory { @@ -104,10 +104,10 @@ class MockReconFactory: public ReconstructorFactory { size_t row_count, ProjectionGeometry proj_geom, VolumeGeometry slice_geom, - VolumeGeometry preview_geom, + VolumeGeometry volume_geom, bool double_buffering) { return std::make_unique( - col_count, row_count, proj_geom, slice_geom, preview_geom, double_buffering); + col_count, row_count, proj_geom, slice_geom, volume_geom, double_buffering); } }; @@ -129,7 +129,7 @@ class ApplicationTest : public testing::Test { size_t buffer_size_ = 100; size_t num_angles_ = 16; size_t slice_size_ = num_cols_; - size_t preview_size_ = slice_size_ / 2; + size_t volume_size_ = slice_size_ / 2; std::string filter_name_ = "shepp"; uint32_t threads_ = 2; @@ -317,9 +317,9 @@ TEST_F(ApplicationTest, TestReconstructing) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); - EXPECT_EQ(dynamic_cast(app_.reconstructor())->num_uploads(), 1); - EXPECT_EQ(dynamic_cast(app_.reconstructor())->num_previews(), 1); - EXPECT_EQ(dynamic_cast(app_.reconstructor())->num_slices(), 0); + EXPECT_EQ(dynamic_cast(app_.reconstructor())->numUploads(), 1); + EXPECT_EQ(dynamic_cast(app_.reconstructor())->numVolumes(), 1); + EXPECT_EQ(dynamic_cast(app_.reconstructor())->numSlices(), 0); } // FIXME: fix Paganin