Skip to content

Commit

Permalink
implement categorical scalar quantities
Browse files Browse the repository at this point in the history
  • Loading branch information
nmwsharp committed Jan 1, 2025
1 parent 477f442 commit c8505a2
Show file tree
Hide file tree
Showing 31 changed files with 642 additions and 81 deletions.
45 changes: 40 additions & 5 deletions examples/demo-app/demo_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,28 @@ void constructDemoCurveNetwork(std::string curveName, std::vector<glm::vec3> nod

{ // Add some node values
std::vector<double> valX(nNodes);
std::vector<double> valNodeCat(nNodes);
std::vector<double> valXabs(nNodes);
std::vector<std::array<double, 3>> randColor(nNodes);
std::vector<glm::vec3> randVec(nNodes);
for (size_t iN = 0; iN < nNodes; iN++) {
valX[iN] = nodes[iN].x;
valNodeCat[iN] = iN * 5 / nNodes;
valXabs[iN] = std::fabs(nodes[iN].x);
randColor[iN] = {{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}};
randVec[iN] = glm::vec3{polyscope::randomUnit() - .5, polyscope::randomUnit() - .5, polyscope::randomUnit() - .5};
}
polyscope::getCurveNetwork(curveName)->addNodeScalarQuantity("nX", valX);
polyscope::getCurveNetwork(curveName)->addNodeScalarQuantity("nXabs", valXabs);
polyscope::getCurveNetwork(curveName)->addNodeScalarQuantity("node categorical", valNodeCat,
polyscope::DataType::CATEGORICAL);
polyscope::getCurveNetwork(curveName)->addNodeColorQuantity("nColor", randColor);
polyscope::getCurveNetwork(curveName)->addNodeVectorQuantity("randVecN", randVec);
}

{ // Add some edge values
std::vector<double> edgeLen(nEdges);
std::vector<double> valEdgeCat(nEdges);
std::vector<std::array<double, 3>> randColor(nEdges);
std::vector<glm::vec3> randVec(nEdges);
for (size_t iE = 0; iE < nEdges; iE++) {
Expand All @@ -77,10 +82,13 @@ void constructDemoCurveNetwork(std::string curveName, std::vector<glm::vec3> nod
size_t nB = std::get<1>(edge);

edgeLen[iE] = glm::length(nodes[nA] - nodes[nB]);
valEdgeCat[iE] = iE * 5 / nEdges;
randColor[iE] = {{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}};
randVec[iE] = glm::vec3{polyscope::randomUnit() - .5, polyscope::randomUnit() - .5, polyscope::randomUnit() - .5};
}
polyscope::getCurveNetwork(curveName)->addEdgeScalarQuantity("edge len", edgeLen, polyscope::DataType::MAGNITUDE);
polyscope::getCurveNetwork(curveName)->addEdgeScalarQuantity("edge categorical", valEdgeCat,
polyscope::DataType::CATEGORICAL);
polyscope::getCurveNetwork(curveName)->addEdgeColorQuantity("eColor", randColor);
polyscope::getCurveNetwork(curveName)->addEdgeVectorQuantity("randVecE", randVec);
}
Expand All @@ -104,6 +112,7 @@ void processFileOBJ(std::string filename) {
auto psMesh = polyscope::registerSurfaceMesh(niceName, vertexPositionsGLM, faceIndices);

auto psSimpleMesh = polyscope::registerSimpleTriangleMesh(niceName, vertexPositionsGLM, faceIndices);
psSimpleMesh->setEnabled(false);

// Useful data
size_t nVertices = psMesh->nVertices();
Expand All @@ -114,12 +123,14 @@ void processFileOBJ(std::string filename) {
std::vector<double> valY(nVertices);
std::vector<double> valZ(nVertices);
std::vector<double> valMag(nVertices);
std::vector<double> valCat(nVertices);
std::vector<std::array<double, 3>> randColor(nVertices);
for (size_t iV = 0; iV < nVertices; iV++) {
valX[iV] = vertexPositionsGLM[iV].x / 10000;
valY[iV] = vertexPositionsGLM[iV].y;
valZ[iV] = vertexPositionsGLM[iV].z;
valMag[iV] = glm::length(vertexPositionsGLM[iV]);
valCat[iV] = (int32_t)(iV * 7 / nVertices) - 2;

randColor[iV] = {{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}};
}
Expand All @@ -129,6 +140,8 @@ void processFileOBJ(std::string filename) {
polyscope::getSurfaceMesh(niceName)->addVertexColorQuantity("vColor", randColor);
polyscope::getSurfaceMesh(niceName)->addVertexScalarQuantity("cY_sym", valY, polyscope::DataType::SYMMETRIC);
polyscope::getSurfaceMesh(niceName)->addVertexScalarQuantity("cNorm", valMag, polyscope::DataType::MAGNITUDE);
polyscope::getSurfaceMesh(niceName)->addVertexScalarQuantity("categorical vert", valCat,
polyscope::DataType::CATEGORICAL);

polyscope::getSurfaceMesh(niceName)->addVertexDistanceQuantity("cY_dist", valY);
polyscope::getSurfaceMesh(niceName)->addVertexSignedDistanceQuantity("cY_signeddist", valY);
Expand All @@ -137,6 +150,7 @@ void processFileOBJ(std::string filename) {
// Add some face scalars
std::vector<double> fArea(nFaces);
std::vector<double> zero(nFaces);
std::vector<double> fCat(nFaces);
std::vector<std::array<double, 3>> fColor(nFaces);
for (size_t iF = 0; iF < nFaces; iF++) {
std::vector<size_t>& face = faceIndices[iF];
Expand All @@ -153,10 +167,13 @@ void processFileOBJ(std::string filename) {

zero[iF] = 0;
fColor[iF] = {{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}};
fCat[iF] = (int32_t)(iF * 25 / nFaces) - 12;
}
polyscope::getSurfaceMesh(niceName)->addFaceScalarQuantity("face area", fArea, polyscope::DataType::MAGNITUDE);
polyscope::getSurfaceMesh(niceName)->addFaceScalarQuantity("zero", zero);
polyscope::getSurfaceMesh(niceName)->addFaceColorQuantity("fColor", fColor);
polyscope::getSurfaceMesh(niceName)->addFaceScalarQuantity("categorical face", fCat,
polyscope::DataType::CATEGORICAL);


// size_t nEdges = psMesh->nEdges();
Expand All @@ -165,15 +182,19 @@ void processFileOBJ(std::string filename) {
std::vector<double> eLen;
std::vector<double> heLen;
std::vector<double> cAngle;
std::vector<double> cID;
std::vector<double> eCat;
std::vector<double> heCat;
std::vector<double> cCat;
std::unordered_set<std::pair<size_t, size_t>, polyscope::hash_combine::hash<std::pair<size_t, size_t>>> seenEdges;
std::vector<uint32_t> edgeOrdering;
for (size_t iF = 0; iF < nFaces; iF++) {
std::vector<size_t>& face = faceIndices[iF];

for (size_t iV = 0; iV < face.size(); iV++) {
size_t i0 = face[iV];
size_t i1 = face[(iV + 1) % face.size()];
size_t im1 = face[(iV + face.size() - 1) % face.size()];
for (size_t iC = 0; iC < face.size(); iC++) {
size_t i0 = face[iC];
size_t i1 = face[(iC + 1) % face.size()];
size_t im1 = face[(iC + face.size() - 1) % face.size()];
glm::vec3 p0 = vertexPositionsGLM[i0];
glm::vec3 p1 = vertexPositionsGLM[i1];
glm::vec3 pm1 = vertexPositionsGLM[im1];
Expand All @@ -188,18 +209,29 @@ void processFileOBJ(std::string filename) {
auto p = std::make_pair(iMin, iMax);
if (seenEdges.find(p) == seenEdges.end()) {
eLen.push_back(len);
eCat.push_back((iF + iC) % 5);
edgeOrdering.push_back(edgeOrdering.size()); // totally coincidentally, this is the trivial ordering
seenEdges.insert(p);
}
heLen.push_back(len);
cAngle.push_back(angle);
heCat.push_back((iF + iC) % 7);
cCat.push_back(i0 % 12);
cID.push_back(iC);
}
}
size_t nEdges = edgeOrdering.size();
polyscope::getSurfaceMesh(niceName)->setEdgePermutation(edgeOrdering);
polyscope::getSurfaceMesh(niceName)->addEdgeScalarQuantity("edge length", eLen);
polyscope::getSurfaceMesh(niceName)->addHalfedgeScalarQuantity("halfedge length", heLen);
polyscope::getSurfaceMesh(niceName)->addCornerScalarQuantity("corner angle", cAngle);
polyscope::getSurfaceMesh(niceName)->addCornerScalarQuantity("corner ID", cID);
polyscope::getSurfaceMesh(niceName)->addEdgeScalarQuantity("categorical edge", eCat,
polyscope::DataType::CATEGORICAL);
polyscope::getSurfaceMesh(niceName)->addHalfedgeScalarQuantity("categorical halfedge", heCat,
polyscope::DataType::CATEGORICAL);
polyscope::getSurfaceMesh(niceName)->addCornerScalarQuantity("categorical corner", cCat,
polyscope::DataType::CATEGORICAL);


// Test error
Expand Down Expand Up @@ -673,13 +705,16 @@ void addDataToPointCloud(std::string pointCloudName, const std::vector<glm::vec3
// Add some scalar quantities
std::vector<double> xC(points.size());
std::vector<std::array<double, 3>> randColor(points.size());
std::vector<double> cat(points.size());
for (size_t i = 0; i < points.size(); i++) {
xC[i] = points[i].x;
randColor[i] = {{polyscope::randomUnit(), polyscope::randomUnit(), polyscope::randomUnit()}};
cat[i] = i * 12 / points.size();
}
polyscope::getPointCloud(pointCloudName)->addScalarQuantity("xC", xC);
polyscope::getPointCloud(pointCloudName)->addColorQuantity("random color", randColor);
polyscope::getPointCloud(pointCloudName)->addColorQuantity("random color2", randColor);
polyscope::getPointCloud(pointCloudName)->addScalarQuantity("categorical", cat, polyscope::DataType::CATEGORICAL);


// Add some vector quantities
Expand Down Expand Up @@ -844,7 +879,7 @@ int main(int argc, char** argv) {
// polyscope::view::windowWidth = 600;
// polyscope::view::windowHeight = 800;
// polyscope::options::maxFPS = -1;
polyscope::options::verbosity = 100;
polyscope::options::verbosity = 5000;
polyscope::options::enableRenderErrorChecks = true;
polyscope::options::allowHeadlessBackends = true;

Expand Down
2 changes: 2 additions & 0 deletions include/polyscope/affine_remapper.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ inline std::string defaultColorMap(DataType type) {
return "coolwarm";
case DataType::MAGNITUDE:
return "blues";
case DataType::CATEGORICAL:
return "hsv";
break;
}
return "viridis";
Expand Down
11 changes: 7 additions & 4 deletions include/polyscope/histogram.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@

namespace polyscope {

// A histogram that shows up in ImGUI
// A histogram that shows up in ImGUI window
// ONEDAY: we could definitely make a better histogram widget for categorical data...

class Histogram {
public:
Histogram(); // must call buildHistogram() with data after
Histogram(std::vector<float>& values); // internally calls buildHistogram()
Histogram(); // must call buildHistogram() with data after
Histogram(std::vector<float>& values, DataType datatype); // internally calls buildHistogram()

~Histogram();

void buildHistogram(const std::vector<float>& values);
void buildHistogram(const std::vector<float>& values, DataType datatype);
void updateColormap(const std::string& newColormap);

// Width = -1 means set automatically
Expand All @@ -33,6 +35,7 @@ class Histogram {
void fillBuffers();
size_t rawHistBinCount = 51;

DataType dataType = DataType::STANDARD;
std::vector<float> rawHistCurveY;
std::vector<std::array<float, 2>> rawHistCurveX;
std::pair<double, double> dataRange;
Expand Down
1 change: 1 addition & 0 deletions include/polyscope/render/color_maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ bool buildColormapSelector(std::string& cm, std::string fieldname = "##colormap_
// - rainbow (CM_RAINBOW)
// - jet (CM_JET)
// - turbo (CM_TURBO)
// - hsv (CM_HSV)
//
// Cyclic:
// - phase (CM_PHASE)
Expand Down
1 change: 1 addition & 0 deletions include/polyscope/render/colormap_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extern const std::vector<glm::vec3> CM_JET;
extern const std::vector<glm::vec3> CM_TURBO;
extern const std::vector<glm::vec3> CM_REDS;
extern const std::vector<glm::vec3> CM_PHASE;
extern const std::vector<glm::vec3> CM_HSV;


} // namespace render
Expand Down
1 change: 1 addition & 0 deletions include/polyscope/render/opengl/shaders/cylinder_shaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extern const ShaderStageSpecification FLEX_CYLINDER_FRAG_SHADER;
// Rules specific to cylinders
extern const ShaderReplacementRule CYLINDER_PROPAGATE_VALUE;
extern const ShaderReplacementRule CYLINDER_PROPAGATE_BLEND_VALUE;
extern const ShaderReplacementRule CYLINDER_PROPAGATE_NEAREST_VALUE;
extern const ShaderReplacementRule CYLINDER_PROPAGATE_COLOR;
extern const ShaderReplacementRule CYLINDER_PROPAGATE_BLEND_COLOR;
extern const ShaderReplacementRule CYLINDER_PROPAGATE_PICK;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace backend_openGL3 {
// High level pipeline
extern const ShaderStageSpecification HISTOGRAM_VERT_SHADER;
extern const ShaderStageSpecification HISTOGRAM_FRAG_SHADER;
extern const ShaderStageSpecification HISTOGRAM_CATEGORICAL_FRAG_SHADER;

// Rules
// extern const ShaderReplacementRule RULE_NAME;
Expand Down
1 change: 1 addition & 0 deletions include/polyscope/render/opengl/shaders/rules.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extern const ShaderReplacementRule SHADE_BASECOLOR; // constant from
extern const ShaderReplacementRule SHADE_COLOR; // from shadeColor
extern const ShaderReplacementRule SHADECOLOR_FROM_UNIFORM;
extern const ShaderReplacementRule SHADE_COLORMAP_VALUE; // colormapped from shadeValue
extern const ShaderReplacementRule SHADE_CATEGORICAL_COLORMAP; // use ints to sample distinct values from colormap
extern const ShaderReplacementRule SHADE_COLORMAP_ANGULAR2; // colormapped from angle of shadeValue2
extern const ShaderReplacementRule SHADE_GRID_VALUE2; // generate a two-color grid with lines from shadeValue2
extern const ShaderReplacementRule SHADE_CHECKER_VALUE2; // generate a two-color checker from shadeValue2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ extern const ShaderReplacementRule MESH_BACKFACE_DARKEN;
extern const ShaderReplacementRule MESH_PROPAGATE_VALUE;
extern const ShaderReplacementRule MESH_PROPAGATE_VALUEALPHA;
extern const ShaderReplacementRule MESH_PROPAGATE_FLAT_VALUE;
extern const ShaderReplacementRule MESH_PROPAGATE_VALUE_CORNER_NEAREST;
extern const ShaderReplacementRule MESH_PROPAGATE_INT;
extern const ShaderReplacementRule MESH_PROPAGATE_VALUE2;
extern const ShaderReplacementRule MESH_PROPAGATE_TCOORD;
Expand Down
64 changes: 44 additions & 20 deletions include/polyscope/scalar_quantity.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ ScalarQuantity<QuantityT>::ScalarQuantity(QuantityT& quantity_, const std::vecto
{
values.checkInvalidValues();
hist.updateColormap(cMap.get());
hist.buildHistogram(values.data);
hist.buildHistogram(values.data, dataType);
// TODO: I think we might be building the histogram ^^^ twice for many quantities

if (vizRangeMin.holdsDefaultValue()) { // min and max should always have same cache state
// dynamically compute a viz range from the data min/max
Expand Down Expand Up @@ -59,16 +60,23 @@ void ScalarQuantity<QuantityT>::buildScalarUI() {
extraText = "This quantity was added as **magnitude** scalar quantity, so only a "
"single symmetric range control can be adjusted, and it must be positive.";
} break;
case DataType::CATEGORICAL: {
extraText = "This quantity was added as **categorical** scalar quantity, it is "
"interpreted as integer labels, each shaded with a distinct color. "
"Range controls are not used, vminmax are used only to set histogram limits, "
"if provided.";
} break;
}
std::string mainText = "The window below shows the colormap used to visualize this scalar, "
"and a histogram of the the data values. The text boxes below show the "
"range limits for the color map.\n\n";
if (dataType != DataType::CATEGORICAL) {
mainText += "To adjust the limit range for the color map, click-and-drag on the text "
"box. Control-click to type a value, even one outside the visible range.";
}
mainText += extraText;
ImGui::SameLine();
ImGuiHelperMarker(("The window below shows the colormap used to visualize this scalar, "
"and a histogram of the the data values. The text boxes below show the "
"range limits for the color map."
"\n\n"
"To adjust the limit range for the color map, click-and-drag on the text "
"box. Control-click to type a value, even one outside the visible range." +
extraText)
.c_str());
ImGuiHelperMarker(mainText.c_str());


// Draw the histogram of values
Expand All @@ -82,7 +90,7 @@ void ScalarQuantity<QuantityT>::buildScalarUI() {
// valid reasons) links the resolution of the slider to the decimal width of the formatted number. When %g formats a
// number with few decimal places, sliders can break. There is no way to set a minimum number of decimal places with
// %g, unfortunately.
{
if (dataType != DataType::CATEGORICAL) {

float imPad = ImGui::GetStyle().ItemSpacing.x;
ImGui::PushItemWidth((histWidth - imPad) / 2);
Expand All @@ -92,11 +100,11 @@ void ScalarQuantity<QuantityT>::buildScalarUI() {
switch (dataType) {
case DataType::STANDARD: {

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

} break;
case DataType::SYMMETRIC: {
Expand All @@ -116,10 +124,13 @@ void ScalarQuantity<QuantityT>::buildScalarUI() {

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

} break;
case DataType::CATEGORICAL: {
// unused
} break;
}

if (changed) {
Expand Down Expand Up @@ -168,12 +179,19 @@ void ScalarQuantity<QuantityT>::buildScalarUI() {
template <typename QuantityT>
void ScalarQuantity<QuantityT>::buildScalarOptionsUI() {
if (ImGui::MenuItem("Reset colormap range")) resetMapRange();
if (ImGui::MenuItem("Enable isolines", NULL, isolinesEnabled.get())) setIsolinesEnabled(!isolinesEnabled.get());
if (dataType != DataType::CATEGORICAL) {
if (ImGui::MenuItem("Enable isolines", NULL, isolinesEnabled.get())) setIsolinesEnabled(!isolinesEnabled.get());
}
}

template <typename QuantityT>
std::vector<std::string> ScalarQuantity<QuantityT>::addScalarRules(std::vector<std::string> rules) {
rules.push_back("SHADE_COLORMAP_VALUE");
if (dataType == DataType::CATEGORICAL) {
rules.push_back("SHADE_CATEGORICAL_COLORMAP");
} else {
// common case
rules.push_back("SHADE_COLORMAP_VALUE");
}
if (isolinesEnabled.get()) {
rules.push_back("ISOLINE_STRIPE_VALUECOLOR");
}
Expand All @@ -183,8 +201,10 @@ std::vector<std::string> ScalarQuantity<QuantityT>::addScalarRules(std::vector<s

template <typename QuantityT>
void ScalarQuantity<QuantityT>::setScalarUniforms(render::ShaderProgram& p) {
p.setUniform("u_rangeLow", vizRangeMin.get());
p.setUniform("u_rangeHigh", vizRangeMax.get());
if (dataType != DataType::CATEGORICAL) {
p.setUniform("u_rangeLow", vizRangeMin.get());
p.setUniform("u_rangeHigh", vizRangeMax.get());
}

if (isolinesEnabled.get()) {
p.setUniform("u_modLen", getIsolineWidth());
Expand All @@ -196,6 +216,7 @@ template <typename QuantityT>
QuantityT* ScalarQuantity<QuantityT>::resetMapRange() {
switch (dataType) {
case DataType::STANDARD:
case DataType::CATEGORICAL:
vizRangeMin = dataRange.first;
vizRangeMax = dataRange.second;
break;
Expand Down Expand Up @@ -285,6 +306,9 @@ double ScalarQuantity<QuantityT>::getIsolineDarkness() {

template <typename QuantityT>
QuantityT* ScalarQuantity<QuantityT>::setIsolinesEnabled(bool newEnabled) {
if (dataType == DataType::CATEGORICAL) {
newEnabled = false; // no isolines allowed for categorical
}
isolinesEnabled = newEnabled;
quantity.refresh();
requestRedraw();
Expand Down
Loading

0 comments on commit c8505a2

Please sign in to comment.