Skip to content
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

Add colormap file support #1309

Merged
merged 8 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/actions/generic-ci/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ runs:
run: |
cmake --install . --component sdk
cmake --install . --component configuration
cmake --install . --component colormaps
Meakk marked this conversation as resolved.
Show resolved Hide resolved

- name: Build and configure python externally
if: |
Expand Down Expand Up @@ -346,7 +347,7 @@ runs:
shell: bash
working-directory: ${{github.workspace}}/install
run: |
${{ env.F3D_BIN_PATH }} ../source/testing/data/suzanne.obj --output=output/install_output.png --ref=../source/.github/baselines/install_output.png --resolution=300,300 --verbose
${{ env.F3D_BIN_PATH }} ../source/testing/data/dragon.vtu --output=output/install_output.png --ref=../source/.github/baselines/install_output.png --resolution=300,300 --colormap-file=viridis --comp=0 --verbose

- name: Check Install plugins
if: |
Expand Down
4 changes: 2 additions & 2 deletions .github/baselines/install_output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 9 additions & 2 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,25 @@ defaults:

-
scope:
path: "doc/user/DESKTOP_INTEGRATION.md"
path: "doc/user/COLOR_MAPS.md"
values:
parent: User Documentation
nav_order: 5

-
scope:
path: "doc/user/DESKTOP_INTEGRATION.md"
values:
parent: User Documentation
nav_order: 6

-
scope:
path: "doc/user/LIMITATIONS_AND_TROUBLESHOOTING.md"
values:
title: Limitations and Troubleshooting
parent: User Documentation
nav_order: 6
nav_order: 7

# libf3d doc
-
Expand Down
8 changes: 8 additions & 0 deletions application/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/F3DConfig.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/F3DConfig.h")

set(F3D_SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/F3DColorMapTools.cxx
${CMAKE_CURRENT_SOURCE_DIR}/F3DConfigFileTools.cxx
${CMAKE_CURRENT_BINARY_DIR}/F3DIcon.cxx
${CMAKE_CURRENT_SOURCE_DIR}/F3DOptionsParser.cxx
Expand Down Expand Up @@ -209,6 +210,13 @@ install(
COMPONENT configuration
EXCLUDE_FROM_ALL)

# Default color maps
install(
DIRECTORY "${F3D_SOURCE_DIR}/resources/colormaps/"
DESTINATION "${f3d_resources_dir}/colormaps"
COMPONENT colormaps
Meakk marked this conversation as resolved.
Show resolved Hide resolved
EXCLUDE_FROM_ALL)

# Other ressoure files
if(UNIX AND NOT APPLE AND NOT ANDROID)
install(FILES "${F3D_SOURCE_DIR}/resources/f3d.desktop"
Expand Down
104 changes: 104 additions & 0 deletions application/F3DColorMapTools.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include "F3DColorMapTools.h"

#include "F3DConfigFileTools.h"

#include "image.h"
#include "log.h"

#include <filesystem>

namespace fs = std::filesystem;

namespace F3DColorMapTools
{
std::string Find(const std::string& str)
{
if (fs::exists(str))
{
fs::path resolved = fs::canonical(str);
if (fs::is_regular_file(resolved))
{
// already full path
return resolved.string();
}
}

std::vector<fs::path> dirsToCheck;
dirsToCheck.emplace_back(F3DConfigFileTools::GetUserConfigFileDirectory() / "colormaps");
#ifdef __APPLE__
dirsToCheck.emplace_back("/usr/local/etc/f3d/colormaps");
#endif
#ifdef __linux__
dirsToCheck.emplace_back("/etc/f3d/colormaps");
dirsToCheck.emplace_back("/usr/share/f3d/colormaps");
#endif
dirsToCheck.emplace_back(F3DConfigFileTools::GetBinaryResourceDirectory() / "colormaps");

for (const fs::path& dir : dirsToCheck)
{
// If the string is a stem, try adding supported extensions
if (fs::path(str).stem() == str)
{
for (const std::string& ext : f3d::image::getSupportedFormats())
{
fs::path cmPath = dir / (str + ext);
if (fs::exists(cmPath))
{
return cmPath.string();
}
}
}
else
{
// If not, use directly
fs::path cmPath = dir / str;
if (fs::exists(cmPath))
{
return cmPath.string();
}
}
}

return {};
}

std::vector<double> Read(const std::string& path)
{
try
{
f3d::image img(path);

if (img.getChannelCount() < 3)
{
f3d::log::error("The specified color map must have at least 3 channels");
return {};
}

if (img.getHeight() != 1)
{
f3d::log::warn("The specified color map height is not equal to 1, only the first row is "
"taken into account");
}

int w = img.getWidth();

std::vector<double> cm(4 * w);

for (int i = 0; i < w; i++)
{
std::vector<double> pixel = img.getNormalizedPixel({ i, 0 });
cm[4 * i + 0] = static_cast<double>(i) / (w - 1);
cm[4 * i + 1] = pixel[0];
cm[4 * i + 2] = pixel[1];
cm[4 * i + 3] = pixel[2];
}

return cm;
}
catch (const f3d::image::read_exception&)
{
f3d::log::error("Cannot read colormap at ", path);
return {};
}
}
}
19 changes: 19 additions & 0 deletions application/F3DColorMapTools.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @class F3DColorMapReader
* @brief A namespace used to convert images to libf3d colormap option
*
*/

#ifndef F3DColorMapReader_h
#define F3DColorMapReader_h

#include <string>
#include <vector>

namespace F3DColorMapTools
{
std::string Find(const std::string& str);
std::vector<double> Read(const std::string& path);
}

#endif
8 changes: 4 additions & 4 deletions application/F3DConfigFileTools.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fs::path F3DConfigFileTools::GetUserConfigFileDirectory()
}

//----------------------------------------------------------------------------
fs::path F3DConfigFileTools::GetBinaryConfigFileDirectory()
fs::path F3DConfigFileTools::GetBinaryResourceDirectory()
{
fs::path dirPath;
try
Expand All @@ -59,9 +59,9 @@ fs::path F3DConfigFileTools::GetBinaryConfigFileDirectory()

// Add binary specific paths
#if F3D_MACOS_BUNDLE
dirPath /= "Resources/configs";
dirPath /= "Resources";
#else
dirPath /= "share/f3d/configs";
dirPath /= "share/f3d";
Meakk marked this conversation as resolved.
Show resolved Hide resolved
#endif
}
catch (const fs::filesystem_error&)
Expand All @@ -88,7 +88,7 @@ fs::path F3DConfigFileTools::GetConfigPath(const std::string& configSearch)
dirsToCheck.emplace_back("/etc/f3d");
dirsToCheck.emplace_back("/usr/share/f3d/configs");
#endif
dirsToCheck.emplace_back(F3DConfigFileTools::GetBinaryConfigFileDirectory());
dirsToCheck.emplace_back(F3DConfigFileTools::GetBinaryResourceDirectory() / "configs");

for (const fs::path& dir : dirsToCheck)
{
Expand Down
3 changes: 1 addition & 2 deletions application/F3DConfigFileTools.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
namespace F3DConfigFileTools
{
std::filesystem::path GetUserConfigFileDirectory();
std::filesystem::path GetBinaryConfigFileDirectory();
std::filesystem::path GetSystemConfigFileDirectory();
Meakk marked this conversation as resolved.
Show resolved Hide resolved
std::filesystem::path GetBinaryResourceDirectory();
std::filesystem::path GetConfigPath(const std::string& configSearch);
}

Expand Down
7 changes: 4 additions & 3 deletions application/F3DOptionsParser.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -307,9 +307,9 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o
this->DeclareOption(grp0, "help", "h", "Print help");
this->DeclareOption(grp0, "version", "", "Print version details");
this->DeclareOption(grp0, "readers-list", "", "Print the list of readers");
this->DeclareOption(grp0, "config", "", "Specify the configuration file to use. absolute/relative path or filename/filestem to search in configuration file locations.", appOptions.UserConfigFile, LocalHasDefaultNo, MayHaveConfig::NO , "<filePath/filename/fileStem>");
this->DeclareOption(grp0, "config", "", "Specify the configuration file to use. absolute/relative path or filename/filestem to search in configuration file locations.", appOptions.UserConfigFile, LocalHasDefaultNo, MayHaveConfig::NO, "<filePath/filename/fileStem>");
this->DeclareOption(grp0, "dry-run", "", "Do not read the configuration file", appOptions.DryRun, HasDefault::YES, MayHaveConfig::NO );
this->DeclareOption(grp0, "no-render", "", "Do not render anything and quit right after loading the first file, use with --verbose to recover information about a file.", appOptions.NoRender, HasDefault::YES, MayHaveConfig::YES );
this->DeclareOption(grp0, "no-render", "", "Do not render anything and quit right after loading the first file, use with --verbose to recover information about a file.", appOptions.NoRender, HasDefault::YES, MayHaveConfig::YES);
this->DeclareOption(grp0, "max-size", "", "Maximum size in Mib of a file to load, negative value means unlimited", appOptions.MaxSize, HasDefault::YES, MayHaveConfig::YES, "<size in Mib>");
this->DeclareOption(grp0, "watch", "", "Watch current file and automatically reload it whenever it is modified on disk", appOptions.Watch, HasDefault::YES, MayHaveConfig::YES );
this->DeclareOption(grp0, "load-plugins", "", "List of plugins to load separated with a comma", appOptions.Plugins, LocalHasDefaultNo, MayHaveConfig::YES, "<paths or names>");
Expand Down Expand Up @@ -380,7 +380,8 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o
this->DeclareOption(grp4, "cells", "c", "Use a scalar array from the cells", options.getAsBoolRef("model.scivis.cells"), HasDefault::YES, MayHaveConfig::YES);
this->DeclareOption(grp4, "range", "", "Custom range for the coloring by array", options.getAsDoubleVectorRef("model.scivis.range"), HasDefault::YES, MayHaveConfig::YES, "<min,max>");
this->DeclareOption(grp4, "bar", "b", "Show scalar bar", options.getAsBoolRef("ui.bar"), HasDefault::YES, MayHaveConfig::YES);
this->DeclareOption(grp4, "colormap", "", "Specify a custom colormap", options.getAsDoubleVectorRef("model.scivis.colormap"), HasDefault::YES, MayHaveConfig::YES, "<color_list>");
this->DeclareOption(grp4, "colormap-file", "", "Specify a colormap image", appOptions.ColorMapFile, LocalHasDefaultNo, MayHaveConfig::YES, "<filePath/filename/fileStem>");
this->DeclareOption(grp4, "colormap", "", "Specify a custom colormap (ignored if \"colormap-file\" is specified)", options.getAsDoubleVectorRef("model.scivis.colormap"), HasDefault::YES, MayHaveConfig::YES, "<color_list>");
this->DeclareOption(grp4, "volume", "v", "Show volume if the file is compatible", options.getAsBoolRef("model.volume.enable"), HasDefault::YES, MayHaveConfig::YES);
this->DeclareOption(grp4, "inverse", "i", "Inverse opacity function for volume rendering", options.getAsBoolRef("model.volume.inverse"), HasDefault::YES, MayHaveConfig::YES);

Expand Down
1 change: 1 addition & 0 deletions application/F3DOptionsParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class options;
struct F3DAppOptions
{
std::string UserConfigFile = "";
std::string ColorMapFile = "";
bool DryRun = false;
bool GeometryOnly = false;
bool GroupGeometries = false;
Expand Down
19 changes: 19 additions & 0 deletions application/F3DStarter.cxx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "F3DStarter.h"

#include "F3DColorMapTools.h"
#include "F3DConfig.h"
#include "F3DIcon.h"
#include "F3DNSDelegate.h"
Expand Down Expand Up @@ -339,6 +340,24 @@ int F3DStarter::Start(int argc, char** argv)
}
#endif
}

// Parse colormap
if (!this->Internals->AppOptions.ColorMapFile.empty())
Meakk marked this conversation as resolved.
Show resolved Hide resolved
{
std::string fullPath = F3DColorMapTools::Find(this->Internals->AppOptions.ColorMapFile);

if (!fullPath.empty())
{
this->Internals->Engine->getOptions().set(
"model.scivis.colormap", F3DColorMapTools::Read(fullPath));
}
else
{
f3d::log::error("Cannot find the colormap ", this->Internals->AppOptions.ColorMapFile);
this->Internals->Engine->getOptions().set("model.scivis.colormap", std::vector<double>{});
}
}

f3d::log::debug("Engine configured");

f3d::log::debug("========== Loading 3D file ==========");
Expand Down
14 changes: 14 additions & 0 deletions application/testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,20 @@ f3d_test(NAME TestConfigOrder DATA suzanne.ply ARGS CONFIG ${F3D_SOURCE_DIR}/tes
f3d_test(NAME TestOutputStream DATA suzanne.ply ARGS --verbose=quiet --output=- REGEXP "^.PNG" NO_BASELINE NO_OUTPUT)
f3d_test(NAME TestOutputStreamInfo DATA suzanne.ply ARGS --verbose=info --output=- REGEXP "redirected to stderr" NO_BASELINE NO_OUTPUT)

# Color map files testing
f3d_test(NAME TestColorMapFileFullPath DATA dragon.vtu ARGS --colormap-file=${F3D_SOURCE_DIR}/testing/data/viridis8.png --scalars --comp=1 DEFAULT_LIGHTS)
f3d_test(NAME TestColorMapInvalid DATA dragon.vtu ARGS --colormap-file=${F3D_SOURCE_DIR}/testing/data/invalid.png --scalars REGEXP "Cannot read colormap at" NO_BASELINE)
f3d_test(NAME TestColorMapNonExistent DATA dragon.vtu ARGS --colormap-file=${F3D_SOURCE_DIR}/testing/data/non_existent.png --scalars REGEXP "Cannot find the colormap" NO_BASELINE)
f3d_test(NAME TestColorMapGrayscale DATA dragon.vtu ARGS --colormap-file=${F3D_SOURCE_DIR}/testing/data/white_grayscale.png --scalars REGEXP "The specified color map must have at least 3 channels" NO_BASELINE)
f3d_test(NAME TestColorMapMore1pxWarning DATA dragon.vtu ARGS --verbose=warning --colormap-file=${F3D_SOURCE_DIR}/testing/data/16bit.png --scalars REGEXP "The specified color map height is not equal to 1" NO_BASELINE)
f3d_test(NAME TestColorMap16bits DATA dragon.vtu ARGS --colormap-file=${F3D_SOURCE_DIR}/testing/data/viridis16.png --scalars --comp=1 DEFAULT_LIGHTS)
f3d_test(NAME TestColorMap32bits DATA dragon.vtu ARGS --colormap-file=${F3D_SOURCE_DIR}/testing/data/viridis32.hdr --scalars --comp=1 DEFAULT_LIGHTS)
if(NOT F3D_MACOS_BUNDLE)
file(COPY "${F3D_SOURCE_DIR}/resources/colormaps/" DESTINATION "${CMAKE_BINARY_DIR}/share/f3d/colormaps")
f3d_test(NAME TestColorMapStem DATA dragon.vtu ARGS --colormap-file=magma --scalars --comp=1 DEFAULT_LIGHTS)
f3d_test(NAME TestColorMapFile DATA dragon.vtu ARGS --colormap-file=magma.png --scalars --comp=1 DEFAULT_LIGHTS)
endif()

# Needs SSBO: https://gitlab.kitware.com/vtk/vtk/-/merge_requests/10675
if(VTK_VERSION VERSION_GREATER_EQUAL 9.3.20231108)
if(APPLE) # MacOS does not support OpenGL 4.3
Expand Down
1 change: 1 addition & 0 deletions doc/dev/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,4 @@ Name|Installed by default|Operating system|Description
`java`|YES|ALL|Java bindings.
`mimetypes`|NO|Linux|Plugins mimetype XML files for integration with Freedesktop.
`assets`|YES|Linux|Assets for integration with Freedesktop.
`colormaps`|NO|ALL|Color maps presets, see [documentation](../user/COLOR_MAPS.md)
62 changes: 62 additions & 0 deletions doc/user/COLOR_MAPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Color maps

## Image files

It is possible to specify a color map using the `--colormap-file` option.
The value of the option can be a path to an image file, a filename or a filestem.
If it is not a path, these directories are used to find the file (in this order):
* Linux: `${XDG_CONFIG_HOME}/f3d/colormaps`, `~/.config/f3d/colormaps`, `/etc/f3d/colormaps`, `/usr/share/f3d/colormaps`, `[install_dir]/share/f3d/colormaps`
* Windows: `%APPDATA%\f3d\colormaps`, `[install_dir]\share\f3d\configs\colormaps`
* macOS: `${XDG_CONFIG_HOME}/f3d/colormaps`, `~/.config/f3d/colormaps`, `/usr/local/etc/f3d/colormaps`, `f3d.app/Contents/Resources/configs/colormaps`
The first existing file found is used.
If it is a filestem, all [supported image](#supported-formats) extensions are tried.

We provide some ready to use color maps files, listed in the table below:

Name|Image
------|------
cividis|<img src="https://github.com/f3d-app/f3d/blob/master/resources/colormaps/cividis.png?raw=true" class="cm" width="256" height="10" />
cubehelix|<img src="https://github.com/f3d-app/f3d/blob/master/resources/colormaps/cubehelix.png?raw=true" class="cm" width="256" height="10" />
gist_earth|<img src="https://github.com/f3d-app/f3d/blob/master/resources/colormaps/gist_earth.png?raw=true" class="cm" width="256" height="10" />
hot|<img src="https://github.com/f3d-app/f3d/blob/master/resources/colormaps/hot.png?raw=true" class="cm" width="256" height="10" />
inferno|<img src="https://github.com/f3d-app/f3d/blob/master/resources/colormaps/inferno.png?raw=true" class="cm" width="256" height="10" />
magma|<img src="https://github.com/f3d-app/f3d/blob/master/resources/colormaps/magma.png?raw=true" class="cm" width="256" height="10" />
plasma|<img src="https://github.com/f3d-app/f3d/blob/master/resources/colormaps/plasma.png?raw=true" class="cm" width="256" height="10" />
seismic|<img src="https://github.com/f3d-app/f3d/blob/master/resources/colormaps/seismic.png?raw=true" class="cm" width="256" height="10" />
viridis|<img src="https://github.com/f3d-app/f3d/blob/master/resources/colormaps/viridis.png?raw=true" class="cm" width="256" height="10" />

It is possible to create a custom color map by creating a simple RGB image in any [supported formats](#supported-formats), and any resolution (if the height is more than 1 row, only the first one is taken into account). The image must be copied in the user config directory:
* Linux/macOS: `~/.config/f3d/colormaps`
* Windows: `%APPDATA%\f3d\colormaps`

### Supported formats

Here's the list of all supported image formats that can be used as color maps:

- `.png`
- `.pnm`, `.pgm`, `.ppm`
- `.tif`, `.tiff`
- `.bmp`
- `.slc`
- `.hdr`
- `.pic`
- `.jpeg`, `.jpg`
- `.MR`
- `.CT`
- `.mhd`, `.mha`
- `.tga`
- `.exr` (if `F3D_MODULE_EXR` is [enabled](../dev/BUILD.md))

## Custom values

If no colormap file is specified, it is also possible to set values manually using the `--colormap` option. A list of numbers between 0 and 1 must be specified. The size of the list is a multiple of 4 and each 4-components tuple correspond to the scalar value, followed by the RGB color.
For example, the default value corresponds to the `hot` preset which can be defined manually with `--colormap=0.0,0.0,0.0,0.0,0.4,0.9,0.0,0.0,0.8,0.9,0.9,0.0,1.0,1.0,1.0,1.0`.
It consists of 4 tuples:
Value|RGB
------|------
0.0|<span style="color:rgb(0,0,0)">&#9724;</span> 0.0, 0.0, 0.0
0.4|<span style="color:rgb(230,0,0)">&#9724;</span> 0.9, 0.0, 0.0
0.8|<span style="color:rgb(230,230,0)">&#9724;</span> 0.9, 0.9, 0.0
1.0|<span style="color:rgb(255,255,255)">&#9724;</span> 1.0, 1.0, 1.0

Values in between are interpolated.
Loading
Loading