#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; namespace { struct Parameters { std::vector names; std::map colors; std::map indices; std::map orientation; std::map opacity; std::map vtkFiles; std::vector fig_129b; std::vector fig_129cd; bool parsedOk{false}; }; /** * Take a string and convert it to lowercase. * * See: https://en.cppreference.com/w/cpp/string/byte/tolower * Only works with ASCII characters. * * @param s: The string to be converted to lowercase. * * @return The lowercase version of the string. */ std::string ToLowerCase(std::string s); /** * Read the parameters from a json file and check that the file paths exist. * * @param fnPath: The path to the json file. * @param parameters: The parameters. */ void ParseJSON(const fs::path fnPath, Parameters& parameters); /** * Create the lookup table for the frog tissues. * * Each table value corresponds the color of one of the frog tissues. * * @param indices: The tissue name and index. * @param colors: The tissue name and color. * @return: The lookup table. */ vtkNew CreateTissueLUT(std::map const& indices, std::map& colors); class SliceOrder { // clang-format off /* These transformations permute image and other geometric data to maintain proper orientation regardless of the acquisition order. After applying these transforms with vtkTransformFilter, a view up of 0, -1, 0 will result in the body part facing the viewer. NOTE: Some transformations have a -1 scale factor for one of the components. To ensure proper polygon orientation and normal direction, you must apply the vtkPolyDataNormals filter. Naming (the nomenclature is medical): si - superior to inferior (top to bottom) is - inferior to superior (bottom to top) ap - anterior to posterior (front to back) pa - posterior to anterior (back to front) lr - left to right rl - right to left */ // clang-format on public: /** * Generate the transforms corresponding to the slice order. */ SliceOrder(); virtual ~SliceOrder() = default; public: /** * Returns the vtkTransform corresponding to the slice order. * * @param sliceOrder: The slice order. * @return The vtkTransform corresponding to the slice order. */ vtkSmartPointer Get(std::string const& sliceOrder); /** * Print the homogenous matrix corresponding to the slice order. * * @param order: The slice order. */ void PrintTransform(std::string const& order); /** * Print all the homogenous matrices corresponding to the slice orders. * */ void PrintAlltransforms(); private: std::map> transform; }; /** * @param scale: Sets the scale and direction of the axes. * @param xyzLabels: Labels for the axes. * @return The axes actor. */ vtkNew MakeAxesActor(std::array& scale, std::array const& xyzLabels); /** * @param cubeLabels: The labels for the cube faces. * @param colors: Used to set the colors of the cube faces. * @return The annotated cube actor. */ vtkNew MakeAnnotatedCubeActor(std::array const& cubeLabels, vtkNamedColors* colors); /** * @param labelSelector: The selector used to define labels for the axes and * cube. * @param colors: Used to set the colors of the cube faces. * @return The combined axes and annotated cube prop. */ vtkNew MakeCubeActor(std::string const& labelSelector, vtkNamedColors* colors); class SliderCallbackOpacity : public vtkCommand { public: static SliderCallbackOpacity* New() { return new SliderCallbackOpacity; } virtual void Execute(vtkObject* caller, unsigned long, void*) { vtkSliderWidget* sliderWidget = reinterpret_cast(caller); double value = static_cast( sliderWidget->GetRepresentation()) ->GetValue(); this->property->SetOpacity(value); } SliderCallbackOpacity() : property(nullptr) { } vtkProperty* property; }; struct SliderProperties { double tubeWidth{0.004}; double sliderLength{0.015}; double sliderWidth{0.008}; double endCapLength{0.008}; double endCapWidth{0.02}; double titleHeight{0.02}; double labelHeight{0.02}; double valueMinimum{0.0}; double valueMaximum{1.0}; double valueInitial{1.0}; std::array p1{0.02, 0.1}; std::array p2{0.18, 0.1}; std::string title; std::string titleColor{"Black"}; std::string labelColor{"Black"}; std::string valueColor{"DarkSlateGray"}; std::string sliderColor{"BurlyWood"}; std::string selectedColor{"Lime"}; std::string barColor{"Black"}; std::string barEndsColor{"Indigo"}; }; /** * @param properties: The slider properties. * @param lut: The color lookup table. * @param idx: The tissue index. * @return The slider widget. */ vtkNew MakeSliderWidget(SliderProperties const& sp, vtkLookupTable* lut, int const& idx); class SliderToggleCallback : public vtkCallbackCommand { public: SliderToggleCallback() = default; static SliderToggleCallback* New() { return new SliderToggleCallback; } /* * Create a vtkCallbackCommand and reimplement it. * */ void Execute(vtkObject* caller, unsigned long /*evId*/, void*) override { // Note the use of reinterpret_cast to cast the caller to the expected type. auto rwi = reinterpret_cast(caller); // Get the keypress std::string key = rwi->GetKeySym(); if (key == "n") { for (auto const& p : *this->sliders) { if (p.second->GetEnabled()) { p.second->Off(); } else { p.second->On(); } } } } /** * For handling the sliders. * * @param sliders The sliders. * */ void SetParameters( std::map>* sliders) { this->sliders = sliders; } private: SliderToggleCallback(const SliderToggleCallback&) = delete; void operator=(const SliderToggleCallback&) = delete; std::map>* sliders{nullptr}; }; } // namespace int main(int argc, char* argv[]) { CLI::App app{"Display all frog parts and translucent skin."}; // Define options std::string fileName; app.add_option("fileName", fileName, "The path to the JSON file e.g. Frog_vtk.json.") ->required() ->check(CLI::ExistingFile); std::vector chosenTissues; app.add_option("-t", chosenTissues, "Select one or more tissues."); auto noSliders = false; app.add_flag("-n", noSliders, "No sliders"); std::array view{false, false, false, false}; auto* ogroup = app.add_option_group( "view", "Select a view corresponding to Fig 12-9 in the VTK Textbook. Only none " "or one of these can be selected."); ogroup->add_flag("-a", view[0], "The view corresponds to Fig 12-9a in the VTK Textbook"); ogroup->add_flag("-b", view[1], "The view corresponds to Fig 12-9b in the VTK Textbook"); ogroup->add_flag("-c", view[2], "The view corresponds to Fig 12-9c in the VTK Textbook"); ogroup->add_flag("-d", view[3], "The view corresponds to Fig 12-9d in the VTK Textbook"); CLI11_PARSE(app, argc, argv); auto selectCount = std::count_if(view.begin(), view.end(), [=](const bool& e) { return e == true; }); if (selectCount > 1) { std::cerr << "Only one or none of the options -a, -b, -c, -d can be selected;" << std::endl; return EXIT_FAILURE; } auto fnPath = fs::path(fileName); if (!fnPath.has_extension()) { fnPath.replace_extension(".json"); } if (!fs::is_regular_file(fnPath)) { std::cerr << "Unable to find: " << fnPath << std::endl; return EXIT_FAILURE; } Parameters parameters; ParseJSON(fnPath, parameters); if (!parameters.parsedOk) { return EXIT_FAILURE; } auto tissues = parameters.names; auto indices = parameters.indices; auto lut = CreateTissueLUT(parameters.indices, parameters.colors); char selectFigure{'\0'}; if (selectCount == 1) { if (view[0]) { selectFigure = 'a'; } else if (view[1]) { // No skin. tissues = parameters.fig_129b; selectFigure = 'b'; } else if (view[2]) { // No skin, blood and skeleton. tissues = parameters.fig_129cd; selectFigure = 'c'; } else { // No skin, blood and skeleton. tissues = parameters.fig_129cd; selectFigure = 'd'; } } if (!chosenTissues.empty()) { for (auto i = 0; i < chosenTissues.size(); ++i) { chosenTissues[i] = ToLowerCase(chosenTissues[i]); } std::vector res; auto has_brainbin{false}; if (std::find(chosenTissues.begin(), chosenTissues.end(), "brainbin") != chosenTissues.end()) { std::cout << "Using brainbin instead of brain." << std::endl; res.push_back("brainbin"); indices.erase("brain"); indices["brainbin"] = 2; parameters.colors.erase("brain"); parameters.colors["brainbin"] = "beige"; has_brainbin = true; } for (auto const& ct : chosenTissues) { if (has_brainbin && (ct == "brain" || ct == "brainbin")) { continue; } if (std::find(tissues.begin(), tissues.end(), ct) != tissues.end()) { res.push_back(ct); } else { std::cout << "Tissue: " << ct << " is not available." << std::endl; return EXIT_FAILURE; } } if (res.size() == 1 && res[0] == "skin") { parameters.opacity["skin"] = 1.0; } tissues = res; } vtkNew colors; colors->SetColor("ParaViewBkg", std::array{82, 87, 110, 255}.data()); // Setup render window, renderer, and interactor. vtkNew ren; vtkNew renWin; renWin->AddRenderer(ren); vtkNew iRen; iRen->SetRenderWindow(renWin); vtkNew style; iRen->SetInteractorStyle(style); SliceOrder so; // so.PrintAlltransforms(); size_t colorSize{0}; for (auto const& p : parameters.colors) { colorSize = std::max(colorSize, p.second.size()); } size_t nameSize{0}; for (auto const& p : parameters.names) { nameSize = std::max(nameSize, p.size()); } std::map> sliders; auto leftStepSize = 1.0 / 9; auto leftPosY = 0.275; auto leftPosX0 = 0.02; auto leftPosX1 = 0.18; auto rightStepSize = 1.0 / 9; auto rightPosY = 0.05; auto rightPosX0 = 0.8 + 0.02; auto rightPosX1 = 0.8 + 0.18; auto sliderCount = 0; std::string line(7 + nameSize + colorSize, '-'); std::cout << line << '\n' << std::setw(nameSize) << std::left << "Tissue" << " Label " << "Color" << '\n' << line << std::endl; auto intSize = 2; for (auto const& tissue : tissues) { vtkNew reader; reader->SetFileName(parameters.vtkFiles[tissue].c_str()); reader->Update(); auto trans = so.Get(parameters.orientation[tissue]); trans->Scale(1, -1, -1); vtkNew tf; tf->SetInputConnection(reader->GetOutputPort()); tf->SetTransform(trans); tf->SetInputConnection(reader->GetOutputPort()); vtkNew normals; normals->SetInputConnection(tf->GetOutputPort()); normals->SetFeatureAngle(60.0); vtkNew mapper; mapper->SetInputConnection(normals->GetOutputPort()); vtkNew actor; actor->SetMapper(mapper); actor->GetProperty()->SetOpacity(parameters.opacity[tissue]); actor->GetProperty()->SetDiffuseColor(lut->GetTableValue(indices[tissue])); actor->GetProperty()->SetSpecular(0.2); actor->GetProperty()->SetSpecularPower(10); ren->AddActor(actor); if (!noSliders) { auto sp = SliderProperties(); sp.valueInitial = parameters.opacity[tissue]; sp.title = tissue; // Screen coordinates. if (sliderCount < 7) { sp.p1[0] = leftPosX0; sp.p1[1] = leftPosY; sp.p2[0] = leftPosX1; sp.p2[1] = leftPosY; leftPosY += leftStepSize; } else { sp.p1[0] = rightPosX0; sp.p1[1] = rightPosY; sp.p2[0] = rightPosX1; sp.p2[1] = rightPosY; rightPosY += rightStepSize; } auto sliderWidget = MakeSliderWidget(sp, lut, parameters.indices[tissue]); sliderWidget->SetInteractor(iRen); sliderWidget->SetAnimationModeToAnimate(); sliderWidget->EnabledOn(); vtkNew cb; cb->property = actor->GetProperty(); sliderWidget->AddObserver(vtkCommand::InteractionEvent, cb); sliders[tissue] = sliderWidget; sliderCount += 1; } std::cout << std::setw(nameSize) << std::left << tissue << " " << std::setw(intSize + 3) << std::right << indices[tissue] << " " << std::setw(colorSize) << std::left << parameters.colors[tissue] << std::endl; } std::cout << line << std::endl; if (noSliders) { renWin->SetSize(1024, 1024); } else { renWin->SetSize(1024 + 400, 1024); } renWin->SetWindowName("FroggieView"); ren->SetBackground(colors->GetColor3d("ParaViewBkg").GetData()); auto camera = ren->GetActiveCamera(); if (selectFigure == '\0') { // Orient so that we look down on the dorsal surface and // the superior surface faces the top of the screen. vtkNew transform; transform->SetMatrix(camera->GetModelTransformMatrix()); transform->RotateY(-90); transform->RotateZ(90); camera->SetModelTransformMatrix(transform->GetMatrix()); ren->ResetCamera(); } else { if (selectFigure == 'a') { // Fig 12-9a in the VTK Textbook camera->SetPosition(495.722368, -447.474954, -646.308030); camera->SetFocalPoint(137.612066, -40.962376, -195.171023); camera->SetViewUp(-0.323882, -0.816232, 0.478398); camera->SetDistance(704.996499); camera->SetClippingRange(319.797039, 1809.449285); } else if (selectFigure == 'b') { // Fig 12-9b in the VTK Textbook camera->SetPosition(478.683494, -420.477744, -643.112038); camera->SetFocalPoint(135.624874, -36.478435, -210.614440); camera->SetViewUp(-0.320495, -0.820148, 0.473962); camera->SetDistance(672.457328); camera->SetClippingRange(307.326771, 1765.990822); } else if (selectFigure == 'c') { // Fig 12-9c in the VTK Textbook camera->SetPosition(201.363313, -147.260834, -229.885066); camera->SetFocalPoint(140.626206, -75.857216, -162.352531); camera->SetViewUp(-0.425438, -0.786048, 0.448477); camera->SetDistance(115.534047); camera->SetClippingRange(7.109870, 854.091718); } else if (selectFigure == 'd') { // Fig 12-9d in the VTK Textbook camera->SetPosition(115.361727, -484.656410, -6.193827); camera->SetFocalPoint(49.126343, 98.501094, 1.323317); camera->SetViewUp(-0.649127, -0.083475, 0.756086); camera->SetDistance(586.955116); camera->SetClippingRange(360.549218, 866.876230); } } std::string labels; if (selectFigure != '\0') { // Superior Anterior Left labels = "sal"; } else { // Right Superior Posterior labels = "rsp"; } vtkNew cow; cow->SetParentRenderer(ren); if (noSliders) { // Turn off if you do not want it. cow->On(); cow->EnabledOn(); } else { cow->Off(); cow->EnabledOff(); } auto axes = MakeCubeActor(labels, colors); vtkNew om; om->SetOrientationMarker(axes); // Position upper left in the viewport. // om->SetViewport(0.0, 0.8, 0.2, 1.0); // Position lower left in the viewport. om->SetViewport(0, 0, 0.2, 0.2); om->SetInteractor(iRen); om->EnabledOn(); om->InteractiveOn(); renWin->Render(); vtkNew sliderToggle; sliderToggle->SetParameters(&sliders); iRen->AddObserver(vtkCommand::KeyPressEvent, sliderToggle); iRen->Start(); return EXIT_SUCCESS; } namespace { std::string ToLowerCase(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); } // correct ); return s; } void ParseJSON(const fs::path fnPath, Parameters& parameters) { std::ifstream ifs(fnPath); Json::Value root; if (ifs) { std::string str; std::string errors; Json::CharReaderBuilder builder{}; auto reader = std::unique_ptr(builder.newCharReader()); std::ostringstream ss; ss << ifs.rdbuf(); // Read in the file comtents str = ss.str(); auto parsingSuccessful = reader->parse(str.c_str(), str.c_str() + str.size(), &root, &errors); ifs.close(); if (!parsingSuccessful) { std::cout << errors << std::endl; parameters.parsedOk = false; return; } parameters.parsedOk = true; } else { std::cerr << "Unable to open: " << fnPath << std::endl; parameters.parsedOk = false; } // Get the parameters that we need. fs::path vtkPath; std::vector fileNames; for (Json::Value::const_iterator outer = root.begin(); outer != root.end(); ++outer) { if (outer.name() == "files") { std::string path; for (Json::Value::const_iterator pth = root["files"].begin(); pth != root["files"].end(); ++pth) { if (pth.name() == "root") { vtkPath = fs::path(pth->asString()); } if (pth.name() == "vtk_files") { for (Json::Value::const_iterator fls = root["files"]["vtk_files"].begin(); fls != root["files"]["vtk_files"].end(); ++fls) { fileNames.push_back(fls->asString()); } } } } if (outer.name() == "tissues") { for (Json::Value::const_iterator tc = root["tissues"]["names"].begin(); tc != root["tissues"]["names"].end(); ++tc) { parameters.names.push_back(tc->asString()); } for (Json::Value::const_iterator tc = root["tissues"]["indices"].begin(); tc != root["tissues"]["indices"].end(); ++tc) { parameters.indices[tc.name()] = tc->asInt(); } for (Json::Value::const_iterator tc = root["tissues"]["colors"].begin(); tc != root["tissues"]["colors"].end(); ++tc) { parameters.colors[tc.name()] = tc->asString(); } for (Json::Value::const_iterator tc = root["tissues"]["indices"].begin(); tc != root["tissues"]["indices"].end(); ++tc) { parameters.indices[tc.name()] = tc->asInt(); } for (Json::Value::const_iterator tc = root["tissues"]["orientation"].begin(); tc != root["tissues"]["orientation"].end(); ++tc) { parameters.orientation[tc.name()] = tc->asString(); } for (Json::Value::const_iterator tc = root["tissues"]["opacity"].begin(); tc != root["tissues"]["opacity"].end(); ++tc) { parameters.opacity[tc.name()] = tc->asDouble(); } } if (outer.name() == "figures") { for (Json::Value::const_iterator c = root["figures"]["fig12-9b"].begin(); c != root["figures"]["fig12-9b"].end(); ++c) { parameters.fig_129b.push_back(c->asString()); } for (Json::Value::const_iterator c = root["figures"]["fig12-9cd"].begin(); c != root["figures"]["fig12-9cd"].end(); ++c) { parameters.fig_129cd.push_back(c->asString()); } } } // Build and check the paths. if (!fileNames.empty()) { if (fileNames.size() != 17) { std::cerr << "Expected seventeen file names."; parameters.parsedOk = false; } else { for (size_t i = 0; i < fileNames.size(); i++) { auto pth = fnPath.parent_path() / vtkPath / fs::path(fileNames[i]); fileNames[i] = pth.make_preferred().string(); if (!(fs::is_regular_file(pth) && fs::exists(pth))) { std::cerr << "Not a file or path does not exist: " << fileNames[i] << std::endl; parameters.parsedOk = false; } else { parameters.vtkFiles[pth.stem().string()] = pth.make_preferred().string(); } } } } else { std::cerr << "Expected .vtk file names in the JSON file."; parameters.parsedOk = false; } } vtkNew CreateTissueLUT(std::map const& indices, std::map& colors) { vtkNew lut; lut->SetNumberOfColors(colors.size()); lut->SetTableRange(0, colors.size() - 1); lut->Build(); vtkNew nc; for (auto const& p : indices) { lut->SetTableValue(p.second, nc->GetColor4d(colors[p.first]).GetData()); } return lut; } SliceOrder::SliceOrder() { vtkNew si_mat; si_mat->Zero(); si_mat->SetElement(0, 0, 1); si_mat->SetElement(1, 2, 1); si_mat->SetElement(2, 1, -1); si_mat->SetElement(3, 3, 1); vtkNew is_mat; is_mat->Zero(); is_mat->SetElement(0, 0, 1); is_mat->SetElement(1, 2, -1); is_mat->SetElement(2, 1, -1); is_mat->SetElement(3, 3, 1); vtkNew lr_mat; lr_mat->Zero(); lr_mat->SetElement(0, 2, -1); lr_mat->SetElement(1, 1, -1); lr_mat->SetElement(2, 0, 1); lr_mat->SetElement(3, 3, 1); vtkNew rl_mat; rl_mat->Zero(); rl_mat->SetElement(0, 2, 1); rl_mat->SetElement(1, 1, -1); rl_mat->SetElement(2, 0, 1); rl_mat->SetElement(3, 3, 1); // The previous transforms assume radiological views of the slices // (viewed from the feet). // Other modalities such as physical sectioning may view from the head. // The following transforms modify the original with a 180° rotation about y vtkNew hf_mat; hf_mat->Zero(); hf_mat->SetElement(0, 0, -1); hf_mat->SetElement(1, 1, 1); hf_mat->SetElement(2, 2, -1); hf_mat->SetElement(3, 3, 1); vtkNew si_trans; si_trans->SetMatrix(si_mat); this->transform["si"] = si_trans; vtkNew is_trans; is_trans->SetMatrix(is_mat); this->transform["is"] = is_trans; vtkNew ap_trans; ap_trans->Scale(1, -1, 1); this->transform["ap"] = ap_trans; vtkNew pa_trans; pa_trans->Scale(1, -1, -1); this->transform["pa"] = pa_trans; vtkNew lr_trans; lr_trans->SetMatrix(lr_mat); this->transform["lr"] = lr_trans; vtkNew rl_trans; lr_trans->SetMatrix(rl_mat); this->transform["rl"] = rl_trans; vtkNew hf_trans; hf_trans->SetMatrix(hf_mat); this->transform["hf"] = hf_trans; vtkNew hf_si_trans; hf_si_trans->SetMatrix(hf_mat); hf_si_trans->Concatenate(si_mat); this->transform["hfsi"] = hf_si_trans; vtkNew hf_is_trans; hf_is_trans->SetMatrix(hf_mat); hf_is_trans->Concatenate(is_mat); this->transform["hfis"] = hf_is_trans; vtkNew hf_ap_trans; hf_ap_trans->SetMatrix(hf_mat); hf_ap_trans->Scale(1, -1, 1); this->transform["hfap"] = hf_ap_trans; vtkNew hf_pa_trans; hf_pa_trans->SetMatrix(hf_mat); hf_pa_trans->Scale(1, -1, -1); this->transform["hfpa"] = hf_pa_trans; vtkNew hf_lr_trans; hf_lr_trans->SetMatrix(hf_mat); hf_lr_trans->Concatenate(lr_mat); this->transform["hflr"] = hf_lr_trans; vtkNew hf_rl_trans; hf_rl_trans->SetMatrix(hf_mat); hf_rl_trans->Concatenate(rl_mat); this->transform["hfrl"] = hf_rl_trans; // Identity this->transform["I"] = vtkNew(); // Zero vtkNew z_trans; z_trans->Scale(0, 0, 0); this->transform["Z"] = z_trans; } void SliceOrder::PrintTransform(std::string const& order) { auto m = this->transform[order]->GetMatrix(); std::ostringstream os; os.setf(std::ios_base::fmtflags(), std::ios_base::floatfield); os << order << '\n'; for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { if (j < 3) { os << std::setw(6) << std::right << std::setprecision(2) << m->GetElement(i, j) << " "; } else { os << std::setw(6) << std::right << m->GetElement(i, j) << '\n'; } } } std::cout << os.str() << '\n'; os.str(""); } void SliceOrder::PrintAlltransforms() { for (auto const &p : this->transform) { PrintTransform(p.first); } } vtkSmartPointer SliceOrder::Get(std::string const& sliceOrder) { return this->transform[sliceOrder]; } vtkNew MakeAxesActor(std::array& scale, std::array const& xyzLabels) { vtkNew axes; axes->SetScale(scale.data()); axes->SetShaftTypeToCylinder(); axes->SetXAxisLabelText(xyzLabels[0].c_str()); axes->SetYAxisLabelText(xyzLabels[1].c_str()); axes->SetZAxisLabelText(xyzLabels[2].c_str()); axes->SetCylinderRadius(0.5 * axes->GetCylinderRadius()); axes->SetConeRadius(1.025 * axes->GetConeRadius()); axes->SetSphereRadius(1.5 * axes->GetSphereRadius()); auto tprop = axes->GetXAxisCaptionActor2D()->GetCaptionTextProperty(); tprop->ItalicOn(); tprop->ShadowOn(); tprop->SetFontFamilyToTimes(); // Use the same text properties on the other two axes. axes->GetYAxisCaptionActor2D()->GetCaptionTextProperty()->ShallowCopy(tprop); axes->GetZAxisCaptionActor2D()->GetCaptionTextProperty()->ShallowCopy(tprop); return axes; } vtkNew MakeAnnotatedCubeActor(std::array const& cubeLabels, vtkNamedColors* colors) { // A cube with labeled faces. vtkNew cube; cube->SetXPlusFaceText(cubeLabels[0].c_str()); cube->SetXMinusFaceText(cubeLabels[1].c_str()); cube->SetYPlusFaceText(cubeLabels[2].c_str()); cube->SetYMinusFaceText(cubeLabels[3].c_str()); cube->SetZPlusFaceText(cubeLabels[4].c_str()); cube->SetZMinusFaceText(cubeLabels[5].c_str()); cube->SetFaceTextScale(0.5); cube->GetCubeProperty()->SetColor(colors->GetColor3d("Gainsboro").GetData()); cube->GetTextEdgesProperty()->SetColor( colors->GetColor3d("LightSlateGray").GetData()); // Change the vector text colors. cube->GetXPlusFaceProperty()->SetColor( colors->GetColor3d("Tomato").GetData()); cube->GetXMinusFaceProperty()->SetColor( colors->GetColor3d("Tomato").GetData()); cube->GetYPlusFaceProperty()->SetColor( colors->GetColor3d("DeepSkyBlue").GetData()); cube->GetYMinusFaceProperty()->SetColor( colors->GetColor3d("DeepSkyBlue").GetData()); cube->GetZPlusFaceProperty()->SetColor( colors->GetColor3d("SeaGreen").GetData()); cube->GetZMinusFaceProperty()->SetColor( colors->GetColor3d("SeaGreen").GetData()); return cube; } vtkNew MakeCubeActor(std::string const& labelSelector, vtkNamedColors* colors) { std::array xyzLabels; std::array cubeLabels; std::array scale; if (labelSelector == "sal") { // xyzLabels = std::array{"S", "A", "L"}; xyzLabels = std::array{"+X", "+Y", "+Z"}; cubeLabels = std::array{"S", "I", "A", "P", "L", "R"}; scale = std::array{1.5, 1.5, 1.5}; } else if (labelSelector == "rsp") { // xyzLabels = std::array{"R", "S", "P"}; xyzLabels = std::array{"+X", "+Y", "+Z"}; cubeLabels = std::array{"R", "L", "S", "I", "P", "A"}; scale = std::array{1.5, 1.5, 1.5}; } else { xyzLabels = std::array{"+X", "+Y", "+Z"}; cubeLabels = std::array{"+X", "-X", "+Y", "-Y", "+Z", "-Z"}; scale = std::array{1.5, 1.5, 1.5}; } // We are combining a vtkAxesActor and a vtkAnnotatedCubeActor // into a vtkPropAssembly auto cube = MakeAnnotatedCubeActor(cubeLabels, colors); auto axes = MakeAxesActor(scale, xyzLabels); // Combine orientation markers into one with an assembly. vtkNew assembly; assembly->AddPart(axes); assembly->AddPart(cube); return assembly; } vtkNew MakeSliderWidget(SliderProperties const& sp, vtkLookupTable* lut, int const& idx) { vtkNew slider; slider->SetMinimumValue(sp.valueMinimum); slider->SetMaximumValue(sp.valueMaximum); slider->SetValue(sp.valueInitial); slider->SetTitleText(sp.title.c_str()); slider->GetPoint1Coordinate()->SetCoordinateSystemToNormalizedDisplay(); slider->GetPoint1Coordinate()->SetValue(sp.p1[0], sp.p1[1]); slider->GetPoint2Coordinate()->SetCoordinateSystemToNormalizedDisplay(); slider->GetPoint2Coordinate()->SetValue(sp.p2[0], sp.p2[1]); slider->SetTubeWidth(sp.tubeWidth); slider->SetSliderLength(sp.sliderLength); slider->SetSliderWidth(sp.sliderWidth); slider->SetEndCapLength(sp.endCapLength); slider->SetEndCapWidth(sp.endCapWidth); slider->SetTitleHeight(sp.titleHeight); slider->SetLabelHeight(sp.labelHeight); vtkNew colors; // Set the colors of the slider components. // Change the color of the bar. slider->GetTubeProperty()->SetColor( colors->GetColor3d(sp.barColor).GetData()); // Change the color of the ends of the bar. slider->GetCapProperty()->SetColor( colors->GetColor3d(sp.barEndsColor).GetData()); // Change the color of the knob that slides. slider->GetSliderProperty()->SetColor( colors->GetColor3d(sp.sliderColor).GetData()); // Change the color of the knob when the mouse is held on it. slider->GetSelectedProperty()->SetColor( colors->GetColor3d(sp.selectedColor).GetData()); // Change the color of the text displaying the value. slider->GetLabelProperty()->SetColor( colors->GetColor3d(sp.valueColor).GetData()); // Use the one color for the labels. // slider->GetTitleProperty()->SetColor(colors->GetColor3d(sp.labelColor)); // Change the color of the text indicating what the slider controls. if (idx >= 0 && idx < 16) { slider->GetTitleProperty()->SetColor(lut->GetTableValue(idx)); slider->GetTitleProperty()->ShadowOff(); } else { slider->GetTitleProperty()->SetColor( colors->GetColor3d(sp.titleColor).GetData()); } vtkNew sliderWidget; sliderWidget->SetRepresentation(slider); return sliderWidget; } } // namespace