Skip to content

Commit

Permalink
improve volume coloring
Browse files Browse the repository at this point in the history
- Add `volume-data` theme that colors positions by volume data
- Add support for color themes to `slice` representation
- Improve/fix palette support in volume color themes
  • Loading branch information
arose committed Jan 4, 2025
1 parent 9f3c617 commit 4d8f009
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 114 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Note that since we don't clearly distinguish between a public and private interf
- Add `external-structure` theme that colors any geometry by structure properties
- Support float and half-float data type for direct-volume rendering and GPU isosurface extraction
- Minor documentation updates
- Add `volume-data` theme that colors positions by volume data
- Add support for color themes to `slice` representation
- Improve/fix palette support in volume color themes

## [v4.10.0] - 2024-12-15

Expand Down
9 changes: 8 additions & 1 deletion src/mol-geo/geometry/color-data.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
Expand All @@ -24,6 +24,7 @@ export type ColorData = {
uColor: ValueCell<Vec3>,
tColor: ValueCell<TextureImage<Uint8Array>>,
tColorGrid: ValueCell<Texture>,
uPaletteDomain: ValueCell<Vec2>,
tPalette: ValueCell<TextureImage<Uint8Array>>,
uColorTexDim: ValueCell<Vec2>,
uColorGridDim: ValueCell<Vec3>,
Expand All @@ -36,6 +37,8 @@ export function createColors(locationIt: LocationIterator, positionIt: LocationI
const data = _createColors(locationIt, positionIt, colorTheme, colorData);
if (colorTheme.palette) {
ValueCell.updateIfChanged(data.dUsePalette, true);
const [min, max] = colorTheme.palette.domain || [0, 1];
ValueCell.update(data.uPaletteDomain, Vec2.set(data.uPaletteDomain.ref.value, min, max));
updatePaletteTexture(colorTheme.palette, data.tPalette);
} else {
ValueCell.updateIfChanged(data.dUsePalette, false);
Expand Down Expand Up @@ -103,6 +106,7 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData
uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(createNullTexture()),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
Expand Down Expand Up @@ -131,6 +135,7 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create(colors),
tColorGrid: ValueCell.create(createNullTexture()),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(colors.width, colors.height)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
Expand Down Expand Up @@ -233,6 +238,7 @@ export function createGridColor(grid: ColorVolume, type: ColorType, colorData?:
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(colors),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(width, height)),
uColorGridDim: ValueCell.create(Vec3.clone(dimension)),
Expand All @@ -255,6 +261,7 @@ function createDirectColor(colorData?: ColorData): ColorData {
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(createNullTexture()),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
Expand Down
3 changes: 2 additions & 1 deletion src/mol-gl/renderable/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
Expand Down Expand Up @@ -203,6 +203,7 @@ export const ColorSchema = {
uColorTexDim: UniformSpec('v2'),
uColorGridDim: UniformSpec('v3'),
uColorGridTransform: UniformSpec('v4'),
uPaletteDomain: UniformSpec('v2'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tColorGrid: TextureSpec('texture', 'rgb', 'ubyte', 'linear'),
Expand Down
6 changes: 4 additions & 2 deletions src/mol-gl/shader/direct-volume.frag.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
Expand Down Expand Up @@ -122,6 +122,7 @@ uniform mat4 uCartnToUnit;
#endif
#ifdef dUsePalette
uniform vec2 uPaletteDomain;
uniform sampler2D tPalette;
#endif
Expand Down Expand Up @@ -271,7 +272,8 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#endif
#if defined(dColorType_direct) && defined(dUsePalette)
material.rgb = texture2D(tPalette, vec2(value, 0.0)).rgb;
float paletteValue = (value - uPaletteDomain[0]) / (uPaletteDomain[1] - uPaletteDomain[0]);
material.rgb = texture2D(tPalette, vec2(clamp(paletteValue, 0.0, 1.0), 0.0)).rgb;
#elif defined(dColorType_uniform)
material.rgb = uColor;
#elif defined(dColorType_instance)
Expand Down
26 changes: 14 additions & 12 deletions src/mol-gl/shader/image.frag.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
Expand All @@ -25,6 +25,10 @@ uniform sampler2D tMarker;
varying vec2 vUv;
varying float vInstance;
#ifdef dUsePalette
uniform sampler2D tPalette;
#endif
#if defined(dInterpolation_catmulrom) || defined(dInterpolation_mitchell) || defined(dInterpolation_bspline)
#define dInterpolation_cubic
#endif
Expand Down Expand Up @@ -99,12 +103,9 @@ void main() {
#else
vec4 imageData = texture2D(tImageTex, vUv);
#endif
imageData.a = clamp(imageData.a, 0.0, 1.0);
if (imageData.a > 0.9) imageData.a = 1.0;
if (imageData.a < 0.5) discard;
imageData.a *= uAlpha;
if (imageData.a < 0.05)
discard;
imageData.a = uAlpha;
float fragmentDepth = gl_FragCoord.z;
Expand All @@ -115,8 +116,7 @@ void main() {
}
#if defined(dRenderVariant_pick)
if (imageData.a < 0.3)
discard;
#include check_picking_alpha
#ifdef requiredDrawBuffers
gl_FragColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
gl_FragData[1] = vec4(packIntToRGB(vInstance), 1.0);
Expand All @@ -133,8 +133,6 @@ void main() {
}
#endif
#elif defined(dRenderVariant_depth)
if (imageData.a < 0.05)
discard;
if (uRenderMask == MaskOpaque) {
gl_FragColor = packDepthToRGBA(fragmentDepth);
} else if (uRenderMask == MaskTransparent) {
Expand All @@ -148,11 +146,11 @@ void main() {
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
if (uMarkingType == 1) {
if (marker > 0.0 || imageData.a < 0.05)
if (marker > 0.0)
discard;
gl_FragColor = packDepthToRGBA(fragmentDepth);
} else {
if (marker == 0.0 || imageData.a < 0.05)
if (marker == 0.0)
discard;
float depthTest = 1.0;
if (uMarkingDepthTest) {
Expand All @@ -164,6 +162,10 @@ void main() {
#elif defined(dRenderVariant_emissive)
gl_FragColor = vec4(0.0);
#elif defined(dRenderVariant_color) || defined(dRenderVariant_tracing)
#ifdef dUsePalette
float v = ((imageData.r * 256.0 * 256.0 * 255.0 + imageData.g * 256.0 * 255.0 + imageData.b * 255.0) - 1.0) / 16777215.0;
imageData.rgb = texture2D(tPalette, vec2(v, 0.0)).rgb;
#endif
gl_FragColor = imageData;
float marker = uMarker;
Expand Down
56 changes: 55 additions & 1 deletion src/mol-model/volume/grid.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
Expand All @@ -8,6 +8,7 @@
import { SpacegroupCell, Box3D, Sphere3D } from '../../mol-math/geometry';
import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { Histogram, calculateHistogram } from '../../mol-math/histogram';
import { lerp } from '../../mol-math/interpolate';

/** The basic unit cell that contains the grid data. */
interface Grid {
Expand Down Expand Up @@ -76,6 +77,59 @@ namespace Grid {
}
return histograms[binCount];
}

export function makeGetTrilinearlyInterpolated(grid: Grid, transform: 'none' | 'relative') {
const cartnToGrid = Grid.getGridToCartesianTransform(grid);
Mat4.invert(cartnToGrid, cartnToGrid);
const gridCoords = Vec3();

const { stats } = grid;
const { dimensions, get } = grid.cells.space;
const data = grid.cells.data;

const [mi, mj, mk] = dimensions;

return function getTrilinearlyInterpolated(position: Vec3): number {
Vec3.copy(gridCoords, position);
Vec3.transformMat4(gridCoords, gridCoords, cartnToGrid);

const i = Math.trunc(gridCoords[0]);
const j = Math.trunc(gridCoords[1]);
const k = Math.trunc(gridCoords[2]);

if (i < 0 || i >= mi || j < 0 || j >= mj || k < 0 || k >= mk) {
return NaN;
}

const u = gridCoords[0] - i;
const v = gridCoords[1] - j;
const w = gridCoords[2] - k;

// Tri-linear interpolation for the value
const ii = Math.min(i + 1, mi - 1);
const jj = Math.min(j + 1, mj - 1);
const kk = Math.min(k + 1, mk - 1);

let a = get(data, i, j, k);
let b = get(data, ii, j, k);
let c = get(data, i, jj, k);
let d = get(data, ii, jj, k);
const x = lerp(lerp(a, b, u), lerp(c, d, u), v);

a = get(data, i, j, kk);
b = get(data, ii, j, kk);
c = get(data, i, jj, kk);
d = get(data, ii, jj, kk);
const y = lerp(lerp(a, b, u), lerp(c, d, u), v);

const value = lerp(x, y, w);
if (transform === 'relative') {
return (value - stats.mean) / stats.sigma;
} else {
return value;
}
};
}
}

export { Grid };
38 changes: 28 additions & 10 deletions src/mol-plugin-ui/controls/parameters.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
Expand Down Expand Up @@ -695,15 +695,33 @@ const colorGradientInterpolated = memoize1((colors: ColorListEntry[]) => {

const colorGradientBanded = memoize1((colors: ColorListEntry[]) => {
const n = colors.length;
const styles: string[] = [`${colorEntryToStyle(colors[0])} ${100 * (1 / n)}%`];
// TODO: does this need to support offsets?
for (let i = 1, il = n - 1; i < il; ++i) {
styles.push(
`${colorEntryToStyle(colors[i])} ${100 * (i / n)}%`,
`${colorEntryToStyle(colors[i])} ${100 * ((i + 1) / n)}%`
);
}
styles.push(`${colorEntryToStyle(colors[n - 1])} ${100 * ((n - 1) / n)}%`);
const styles: string[] = [];

const hasOffsets = colors.every(c => Array.isArray(c));
if (hasOffsets) {
const off = colors as [Color, number][];
styles.push(`${Color.toStyle(off[0][0])} ${(100 * off[0][1]).toFixed(2)}%`);
for (let i = 0, il = off.length - 1; i < il; ++i) {
const [c0, o0] = off[i];
const [c1, o1] = off[i + 1];
const o = o0 + (o1 - o0) / 2;
styles.push(
`${Color.toStyle(c0)} ${(100 * o).toFixed(2)}%`,
`${Color.toStyle(c1)} ${(100 * o).toFixed(2)}%`
);
}
styles.push(`${Color.toStyle(off[off.length - 1][0])} ${(100 * off[off.length - 1][1]).toFixed(2)}%`);
} else {
const styles: string[] = [`${colorEntryToStyle(colors[0])} ${100 * (1 / n)}%`];
for (let i = 1, il = n - 1; i < il; ++i) {
styles.push(
`${colorEntryToStyle(colors[i])} ${100 * (i / n)}%`,
`${colorEntryToStyle(colors[i])} ${100 * ((i + 1) / n)}%`
);
}
styles.push(`${colorEntryToStyle(colors[n - 1])} ${100 * ((n - 1) / n)}%`);
}

return `linear-gradient(to right, ${styles.join(', ')})`;
});

Expand Down
Loading

0 comments on commit 4d8f009

Please sign in to comment.