Skip to content

Commit

Permalink
make scalar minmax limits persistent (#250)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmwsharp authored Jan 14, 2024
1 parent f8e3509 commit acc6afc
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 28 deletions.
27 changes: 21 additions & 6 deletions include/polyscope/persistent_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,13 @@ class PersistentValue {
PersistentValue(const std::string& name_, T value_) : name(name_), value(value_) {
if (detail::getPersistentCacheRef<T>().cache.find(name) != detail::getPersistentCacheRef<T>().cache.end()) {
value = detail::getPersistentCacheRef<T>().cache[name];
holdsDefaultValue = false;
holdsDefaultValue_ = false;
} else {
// Update cache value
detail::getPersistentCacheRef<T>().cache[name] = value;
}
}

// Ensure in cache on deletion (see not above reference conversion)
~PersistentValue() {}

// Don't want copy or move constructors, only operators
Expand Down Expand Up @@ -85,32 +84,48 @@ class PersistentValue {
// NOTE if you write via this reference, the value will not _actually_ be cached until
// manuallyChanged() is called, rather than immediately (ugly, but seems necessary to use with imgui)
T& get() { return value; }

// Mark that a value has been directly written via the get() reference, and should be cached
void manuallyChanged() { set(value); }

// clears any cached value, but does not change the current value of the variable
void clearCache() {
detail::getPersistentCacheRef<T>().cache.erase(name);
holdsDefaultValue_ = true;
}

// Explicit setter, which takes care of storing in cache
void set(T value_) {
value = value_;
detail::getPersistentCacheRef<T>().cache[name] = value;
holdsDefaultValue = false;
holdsDefaultValue_ = false;
}

// Passive setter, will change value without marking in cache; does nothing if some value has already been directly
// set (equivalent to constructing with a different value).
void setPassive(T value_) {
if (holdsDefaultValue) {
if (holdsDefaultValue_) {
value = value_;
detail::getPersistentCacheRef<T>().cache[name] = value;
}
}

bool holdsDefaultValue() const { return holdsDefaultValue_; }

// Make all template variants friends, so conversion can access private members
template <typename>
friend class PersistentValue;

protected:
// the name of the value
const std::string name;

// the value
T value;
bool holdsDefaultValue = true; // True if the value was set on construction and never changed. False if it was pulled
// from cache or has ever been explicitly set

// True if the value was set on construction or passively and never changed. False if
// it was pulled from cache or has ever been explicitly set
bool holdsDefaultValue_ = true;
};

// clang-format off
Expand Down
3 changes: 2 additions & 1 deletion include/polyscope/scalar_quantity.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ class ScalarQuantity {
// === Visualization parameters

// Affine data maps and limits
std::pair<float, float> vizRange; // TODO make these persistent
std::pair<double, double> dataRange;
PersistentValue<float> vizRangeMin;
PersistentValue<float> vizRangeMax;
Histogram hist;

// Parameters
Expand Down
63 changes: 44 additions & 19 deletions include/polyscope/scalar_quantity.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ template <typename QuantityT>
ScalarQuantity<QuantityT>::ScalarQuantity(QuantityT& quantity_, const std::vector<float>& values_, DataType dataType_)
: quantity(quantity_), values(&quantity, quantity.uniquePrefix() + "values", valuesData), valuesData(values_),
dataType(dataType_), dataRange(robustMinMax(values.data, 1e-5)),
vizRangeMin(quantity.uniquePrefix() + "vizRangeMin", -777.), // set later,
vizRangeMax(quantity.uniquePrefix() + "vizRangeMax", -777.), // including clearing cache
cMap(quantity.uniquePrefix() + "cmap", defaultColorMap(dataType)),
isolinesEnabled(quantity.uniquePrefix() + "isolinesEnabled", false),
isolineWidth(quantity.uniquePrefix() + "isolineWidth",
Expand All @@ -17,7 +19,12 @@ ScalarQuantity<QuantityT>::ScalarQuantity(QuantityT& quantity_, const std::vecto
{
hist.updateColormap(cMap.get());
hist.buildHistogram(values.data);
resetMapRange();

if (vizRangeMin.holdsDefaultValue()) { // min and max should always have same cache state
// dynamically compute a viz range from the data min/max
// note that this also clears the persistent value's cahce, so it's like it was never set
resetMapRange();
}
}

template <typename QuantityT>
Expand Down Expand Up @@ -62,7 +69,7 @@ void ScalarQuantity<QuantityT>::buildScalarUI() {


// Draw the histogram of values
hist.colormapRange = vizRange;
hist.colormapRange = std::pair<float, float>(vizRangeMin.get(), vizRangeMax.get());
float windowWidth = ImGui::GetWindowWidth();
float histWidth = 0.75 * windowWidth;
hist.buildUI(histWidth);
Expand All @@ -77,36 +84,47 @@ void ScalarQuantity<QuantityT>::buildScalarUI() {
float imPad = ImGui::GetStyle().ItemSpacing.x;
ImGui::PushItemWidth((histWidth - imPad) / 2);
float speed = (dataRange.second - dataRange.first) / 100.;
bool changed = false;

switch (dataType) {
case DataType::STANDARD: {

ImGui::DragFloat("##min", &vizRange.first, speed, dataRange.first, vizRange.second, "%.5g",
ImGuiSliderFlags_NoRoundToFormat);
changed = changed || ImGui::DragFloat("##min", &vizRangeMin.get(), speed, dataRange.first, vizRangeMax.get(),
"%.5g", ImGuiSliderFlags_NoRoundToFormat);
ImGui::SameLine();
ImGui::DragFloat("##max", &vizRange.second, speed, vizRange.first, dataRange.second, "%.5g",
ImGuiSliderFlags_NoRoundToFormat);
changed = changed || ImGui::DragFloat("##max", &vizRangeMax.get(), speed, vizRangeMin.get(), dataRange.second,
"%.5g", ImGuiSliderFlags_NoRoundToFormat);

} break;
case DataType::SYMMETRIC: {
float absRange = std::max(std::abs(dataRange.first), std::abs(dataRange.second));

if (ImGui::DragFloat("##min", &vizRange.first, speed, -absRange, 0.f, "%.5g", ImGuiSliderFlags_NoRoundToFormat)) {
vizRange.second = -vizRange.first;
if (ImGui::DragFloat("##min", &vizRangeMin.get(), speed, -absRange, 0.f, "%.5g",
ImGuiSliderFlags_NoRoundToFormat)) {
vizRangeMax.get() = -vizRangeMin.get();
changed = true;
}
ImGui::SameLine();
if (ImGui::DragFloat("##max", &vizRange.second, speed, 0.f, absRange, "%.5g", ImGuiSliderFlags_NoRoundToFormat)) {
vizRange.first = -vizRange.second;
if (ImGui::DragFloat("##max", &vizRangeMax.get(), speed, 0.f, absRange, "%.5g",
ImGuiSliderFlags_NoRoundToFormat)) {
vizRangeMin.get() = -vizRangeMax.get();
changed = true;
}

} break;
case DataType::MAGNITUDE: {
ImGui::DragFloat("##max", &vizRange.second, speed, 0.f, dataRange.second, "%.5g",
ImGuiSliderFlags_NoRoundToFormat);
changed = changed || ImGui::DragFloat("##max", &vizRangeMax.get(), speed, 0.f, dataRange.second, "%.5g",
ImGuiSliderFlags_NoRoundToFormat);

} break;
}

if (changed) {
vizRangeMin.manuallyChanged();
vizRangeMax.manuallyChanged();
requestRedraw();
}

ImGui::PopItemWidth();
}

Expand Down Expand Up @@ -162,8 +180,8 @@ std::vector<std::string> ScalarQuantity<QuantityT>::addScalarRules(std::vector<s

template <typename QuantityT>
void ScalarQuantity<QuantityT>::setScalarUniforms(render::ShaderProgram& p) {
p.setUniform("u_rangeLow", vizRange.first);
p.setUniform("u_rangeHigh", vizRange.second);
p.setUniform("u_rangeLow", vizRangeMin.get());
p.setUniform("u_rangeHigh", vizRangeMax.get());

if (isolinesEnabled.get()) {
p.setUniform("u_modLen", getIsolineWidth());
Expand All @@ -175,17 +193,23 @@ template <typename QuantityT>
QuantityT* ScalarQuantity<QuantityT>::resetMapRange() {
switch (dataType) {
case DataType::STANDARD:
vizRange = dataRange;
vizRangeMin = dataRange.first;
vizRangeMax = dataRange.second;
break;
case DataType::SYMMETRIC: {
double absRange = std::max(std::abs(dataRange.first), std::abs(dataRange.second));
vizRange = std::make_pair(-absRange, absRange);
vizRangeMin = -absRange;
vizRangeMax = absRange;
} break;
case DataType::MAGNITUDE:
vizRange = std::make_pair(0., dataRange.second);
vizRangeMin = 0.;
vizRangeMax = dataRange.second;
break;
}

vizRangeMin.clearCache();
vizRangeMax.clearCache();

requestRedraw();
return &quantity;
}
Expand Down Expand Up @@ -214,13 +238,14 @@ std::string ScalarQuantity<QuantityT>::getColorMap() {

template <typename QuantityT>
QuantityT* ScalarQuantity<QuantityT>::setMapRange(std::pair<double, double> val) {
vizRange = val;
vizRangeMin = val.first;
vizRangeMax = val.second;
requestRedraw();
return &quantity;
}
template <typename QuantityT>
std::pair<double, double> ScalarQuantity<QuantityT>::getMapRange() {
return vizRange;
return std::pair<float, float>(vizRangeMin.get(), vizRangeMax.get());
}
template <typename QuantityT>
std::pair<double, double> ScalarQuantity<QuantityT>::getDataRange() {
Expand Down
4 changes: 2 additions & 2 deletions src/volume_grid_scalar_quantity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ void VolumeGridNodeScalarQuantity::buildCustomUI() {

// Set isovalue
ImGui::PushItemWidth(120);
if (ImGui::SliderFloat("##Radius", &isosurfaceLevel.get(), vizRange.first, vizRange.second, "%.4e")) {
if (ImGui::SliderFloat("##Radius", &isosurfaceLevel.get(), vizRangeMin.get(), vizRangeMax.get(), "%.4e")) {
// Note: we intentionally do this rather than calling setIsosurfaceLevel(), because that function immediately
// recomputes the level set mesh, which is too expensive during user interaction
// recomputes the levelset mesh, which is too expensive during user interaction
isosurfaceLevel.manuallyChanged();
}
ImGui::PopItemWidth();
Expand Down
25 changes: 25 additions & 0 deletions test/src/misc_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,31 @@

#include "polyscope_test.h"

// ============================================================
// =============== Scalar Quantity Tests
// ============================================================

// We test these on a point cloud because it is convenient, but really we are testing the scalar quantity

TEST_F(PolyscopeTest, TestScalarQuantity) {
auto psPoints = registerPointCloud();

std::vector<double> vScalar(psPoints->nPoints(), 7.);
auto q1 = psPoints->addScalarQuantity("vScalar", vScalar);
q1->setEnabled(true);
polyscope::show(3);

// get map range
std::pair<double, double> newRange = {-1., 1.};
q1->setMapRange(newRange);
EXPECT_EQ(newRange, q1->getMapRange());


polyscope::show(3);

polyscope::removeAllStructures();
}

// ============================================================
// =============== Materials tests
// ============================================================
Expand Down

0 comments on commit acc6afc

Please sign in to comment.