-
Notifications
You must be signed in to change notification settings - Fork 226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Color Maps #752
Color Maps #752
Conversation
@mborland : I like the Two comments: What about:
rather than
? I wonder if the latter is too verbose. Next, will we get any performance wins if we use:
rather than:
? I also wonder if we should be using the move constructor, since these color tables are generally known at compile time. I think the tables can be Is there a concept for a |
[ci skip]
|
So you're thinking the users will do:
and the less verbose syntax will occur in user code, rather than in ours?
Well, you might get gains from having the referenced elements being contiguous in memory, but of course it won't change any fundamental complexity . . . ... If these are all |
Yes, then all the user has to do is call
To me derived classes was the easiest way to eliminate code repetition. It also allowed for an easy implementation of You mentioned using boost.GIL for an example. Do you have one written already I could edit to make this work? I have never used GIL before. |
Actually I've never used it either. We can even write a .pnm to a textfile and then screenshot it for the docs; another option is to use lodepng; that's what I did here. Let me think of a 2D function which would nicely get plotted with these color maps. This one might be good to show off viridis; the code to reproduce it is here. @jzmaddock , @ckormanyos : Can you think of anything that could be displayed really nicely using a color map? |
[ci skip]
@NAThompson I ended up modifying your example from DRT, and get the following output: Also here is where the color table object is constructed, and here is where it is used. I don't think the calls are overly verbose. |
@mborland : That looks awesome! Looks like it wasn't too much work to get there either . . . I agree that the calls aren't too verbose, however I do wonder who will actually care to provide their own color tables. These color tables have been through extensive vetting by psychologists who measure their interpretive power. I do worry that if people do provide their own color tables, they'll use something like rainbow which is known to be terrible for scientific interpretation. |
@mborland : It occurs to me that the user might want to try multiple color tables without recompilation, say, via a command line flag. Here's my first attempt: diff --git a/example/color_maps_example.cpp b/example/color_maps_example.cpp
index 23b3a3782..b46d84a0d 100644
--- a/example/color_maps_example.cpp
+++ b/example/color_maps_example.cpp
@@ -130,9 +130,21 @@ private:
Real ymin_;
};
-int main()
+int main(int argc, char** argv)
{
- using Real = long double;
+ using Real = double;
+ if (argc <= 1) {
+ std::cerr << "Usage: ./color_map {color_table}, where {color_table} is one of viridis, kindlemann, plasma, or ..\n";
+ return 1;
+ }
+ std::function<std::array<Real, 3>(Real)> color_map;
+ if (argv[1] == "viridis") {
+ color_map = boost::math::tools::viridis_color_map<Real>;
+ }
+ else {
+ std::cerr << "Could not recognize color map " << argv[1] << " options are viridis, kindlemann, plasma . . .\n";
+ return 1;
+ }
constexpr int64_t image_width = 4096;
constexpr int64_t image_height = 4096;
std::vector<uint8_t> img(4*image_width*image_height, 0); but this doesn't really work . . . |
{ | ||
public: | ||
constexpr plasma_color_map() : | ||
detail::color_map<Real>({0.18500126283629117,0.1911189044113562,0.19712864908128114,0.2030416233571155,0.20887268107458867,0.21463072331602523,0.22032157730150803,0.2259575605729931,0.23155039796588678,0.23710641646603325,0.24262940495715968,0.24812510063567056,0.25359583439603345,0.259047386098491,0.26447987886309154,0.2698981057405734,0.2753043830681906,0.28069900692141886,0.28608641029403303,0.2914638822295278,0.29683511768078935,0.3021991776471102,0.307559383374685,0.31291664864816615,0.31826993831935324,0.3236225760753923,0.3289709965431237,0.33431870303843003,0.33966373680896456,0.3450078077262057,0.35034847407826647,0.3556874153529715,0.36102581201779066,0.3663615341003603,0.37169632394652985,0.3770276964703286,0.382357946820387,0.38768438207138256,0.3930062860759083,0.3983273716748813,0.4036425817932003,0.40895431552409317,0.41426001622424247,0.4195623952577179,0.4248563616485503,0.4301441775161412,0.4354268819265687,0.4406999402134,0.44596644643576516,0.45122228710713624,0.4564697629199718,0.4617056654598785,0.4669310023070961,0.47214475738324385,0.4773458407394621,0.48253461632433253,0.4877090328365206,0.49286905392912533,0.49801338714995985,0.5031412603622233,0.5082531828521651,0.5133462124834312,0.5184220260592395,0.5234778559822143,0.5285143492877827,0.5335306956538576,0.5385244789001884,0.5434980954707963,0.5484481027774952,0.5533750875909487,0.5582766534405778,0.5631554062153734,0.5680081900745759,0.5728333975138157,0.5776342744306816,0.5824076737546731,0.5871533984089229,0.5918701205096012,0.5965604367679973,0.6012201835310419,0.6058512183412395,0.6104521263820015,0.6150225220246975,0.6195628616073623,0.6240719801812181,0.6285513580399021,0.6329989221793823,0.6374162963015114,0.6418004809769666,0.6461536800309777,0.650476135769243,0.6547651168217998,0.6590245256639604,0.6632516551223815,0.6674477291126537,0.6716108031251352,0.6757429658756737,0.6798454057970371,0.6839159522860683,0.6879568949603344,0.6919652822236589,0.6959450042288188,0.6998941469583252,0.7038136863269572,0.7077036076142078,0.7115648684573126,0.7153974874768007,0.7192003105763611,0.7229763232162184,0.7267244865470727,0.7304437173416366,0.7341377518333918,0.7378037499188499,0.7414454244046753,0.7450587348350122,0.7486483346580772,0.7522104746641185,0.755748680542265,0.7592629017974487,0.7627520603974456,0.766216825373388,0.7696582925955131,0.773077107895631,0.7764713899964052,0.7798428195281752,0.7831920638854646,0.7865182091233281,0.7898228934175395,0.7931051428523151,0.7963656261858302,0.7996034565968984,0.8028201310987728,0.8060159189746301,0.8091884512009491,0.8123413974246995,0.8154719410234268,0.8185825533178849,0.8216705444481089,0.8247373765508464,0.8277822680188923,0.8308060259837033,0.8338091392025193,0.8367899778785102,0.8397489706421578,0.8426854482941508,0.8456008173688659,0.8484934838710892,0.8513631273192183,0.8542102862752687,0.8570334035212495,0.8598348399896373,0.8626110724885258,0.8653643579173034,0.8680923147108822,0.8707954368901418,0.8734743717670276,0.8761266641066531,0.8787535738894046,0.8813526771710705,0.8839261632148947,0.8864707162026111,0.8889878746971145,0.8914771762860599,0.8939362684731502,0.896366404995977,0.8987661885216094,0.9011348456797987,0.9034710462469783,0.9057762624345291,0.9080481396866358,0.9102863527112363,0.9124909968821041,0.9146608551755517,0.9167960662705978,0.9188933135427245,0.9209542277904884,0.9229781644169556,0.9249630587811338,0.9269098852966564,0.928815526839391,0.9306829366142825,0.9325070229302802,0.9342897265833519,0.9360290542859102,0.9377243972740386,0.9393770933973468,0.9409842661949558,0.9425456696997273,0.9440594310662802,0.9455271344382453,0.9469451155697922,0.9483136189409812,0.9496329509923357,0.9509013387619204,0.9521193680507284,0.9532833326102016,0.9543957893212712,0.9554530885899511,0.9564564274893136,0.9574039803720095,0.9582930778369493,0.9591252451658657,0.9598979045875052,0.9606124721276129,0.9612663555645452,0.9618587615061226,0.9623887555724971,0.9628559105126825,0.9632594414845331,0.9635970174364671,0.9638686962955997,0.964073330664618,0.9642088982457305,0.9642762050566095,0.9642722292530789,0.9641978734579372,0.9640500241140576,0.9638285003224122,0.9635333683894992,0.9631614522931872,0.962712538770477,0.9621866932888067,0.9615816256657105,0.960894345402275,0.9601234461397462,0.959270237581007,0.958333448735586,0.9573126236007495,0.9562062303425974,0.9550135580216242,0.9537297610630108,0.9523531724953679,0.9508874276947369,0.9493337129963176,0.9476874510448859,0.9459406787196255,0.944103300221897,0.9421780061800054,0.9401426587007371,0.9380209885848168,0.9358000817994506,0.9334816723713976,0.9310720730839354,0.9285606186650813,0.9259594465149981,0.92327053647064,0.9204816890380406,0.9176163146663037,0.9146765909646813,0.9116700174610778,0.9086068264551928,0.9055122790291467,0.9024220298268155,0.8994080090565665,0.8965597700566809,0.894058310302958}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I think of it, I doubt we're even coming close to achieving double precision resolution. Would it make sense to replace these character by a 32 bit hexfloat representation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could. I don't think there are many displays in circulation that have a 32 bit color depth.
@mborland : I personally like huge images but the maintainers of the global boost package will complain if we add a 1.3MB file to the distribution. Could we make the image smaller? Also could you drop a benchmark into |
Can the color maps be used on histogram data from fractal iterations such as Mandelbrot set? |
I have a non-published project that uses Boost.Gil in combination with a jpeg library that I made mostly to be portable as well. I can't find the origin of the original source code of my ported jpeg library any more. It seems to come from last century. I don't know if it can be redistributed. Aside from the jpeg LIB potential availability/portability problem, I ended up going from |
Of course! |
@NAThompson Here is the performance data
|
@mborland : Shouldn't the call be constant time? |
@NAThompson This is now clean with CI. Any other changes, or good to merge? |
I still want to check if we can make the color data an array of structs, rather than three arrays, and see how it affects the performance. Gimme a few days . . . |
dee1d44
to
55b9607
Compare
Ok I managed to hack up a script to get the color tables into an array of structs: using CSV
using DataFrames
csv_reader = CSV.File("plasma-table-float-0256.csv")
print("std::array<std::array<double, 3>, 256> plasma_data_ = {{")
for row in csv_reader
println("{$(row.RGB_r), $(row.RGB_g), $(row.RGB_b)}, ")
end
print("}};") @mborland : Accidently amend committed on a shared branch; my bad. Could you rerun the performance benchmark on your machine? I'm getting:
Using the array of structs, I can get:
This is consistent with the amount of memory pressure being larger for doubles. It also makes it so we can dynamically bind a I had to remove the Nonetheless, here's an example that pumps out swatches of the color tables: // (C) Copyright Nick Thompson 2021.
// (C) Copyright Matt Borland 2022.
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <array>
#include <boost/math/tools/color_maps.hpp>
#include <cmath>
#include <complex>
#include <cstdint>
#include <iostream>
#include <limits>
#include <tuple>
#include <vector>
#if !__has_include("lodepng.h")
#error "lodepng.h is required to run this example."
#endif
#include "lodepng.h"
#include <iostream>
#include <string>
#include <vector>
// In lodepng, the vector is expected to be row major, with the top row
// specified first. Note that this is a bit confusing sometimes as it's more
// natural to let y increase moving *up*.
unsigned write_png(const std::string &filename,
const std::vector<std::uint8_t> &img, std::size_t width,
std::size_t height) {
unsigned error = lodepng::encode(filename, img, width, height,
LodePNGColorType::LCT_RGBA, 8);
if (error) {
std::cerr << "Error encoding png: " << lodepng_error_text(error) << "\n";
}
return error;
}
int main(int argc, char **argv) {
using Real = double;
using boost::math::tools::viridis;
using std::sqrt;
std::function<std::array<Real, 3>(Real)> color_map = viridis<Real>;
std::string requested_color_map = "viridis";
if (argc == 2) {
requested_color_map = std::string(argv[1]);
if (requested_color_map == "smooth_cool_warm") {
color_map = boost::math::tools::smooth_cool_warm<Real>;
} else if (requested_color_map == "plasma") {
color_map = boost::math::tools::plasma<Real>;
} else if (requested_color_map == "black_body") {
color_map = boost::math::tools::black_body<Real>;
} else if (requested_color_map == "inferno") {
color_map = boost::math::tools::inferno<Real>;
} else if (requested_color_map == "kindlmann") {
color_map = boost::math::tools::kindlmann<Real>;
} else if (requested_color_map == "extended_kindlmann") {
color_map = boost::math::tools::extended_kindlmann<Real>;
} else {
std::cerr << "Could not recognize color map " << argv[1] << ".";
return 1;
}
}
constexpr int64_t image_width = 1024;
constexpr int64_t image_height = 10;
std::vector<std::uint8_t> img(4 * image_width * image_height, 0);
for (int64_t j = 0; j < image_height; ++j) {
std::cout << "j = " << j << "\n";
for (int64_t i = 0; i < image_width; ++i) {
double t = i / (image_width - 1.0);
auto c = boost::math::tools::to_8bit_rgba(color_map(t));
int64_t idx = 4 * image_width * (image_height - 1 - j) + 4 * i;
img[idx + 0] = c[0];
img[idx + 1] = c[1];
img[idx + 2] = c[2];
img[idx + 3] = c[3];
}
}
// Requires lodepng.h
// See: https://github.com/lvandeve/lodepng for download and compilation
// instructions
write_png(requested_color_map + "_swatch.png", img, image_width,
image_height);
} |
@mborland : Nice diffs. I think I'll put swatches for all the color maps in the docs and then we'll be done. |
@mborland : Could you put eyes on the docs for me then squash merge once you're happy? |
[ci skip]
Docs look good to me now so merging. |
@NAThompson Here's my cut at implementing color tables without as much code duplication. I also think this is a good opportunity to start using C++20 concepts. Compiler support seems pretty good.