Skip to content

Commit

Permalink
Fixed color scale issues
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenthoms committed Jun 17, 2024
1 parent b3aa848 commit 7c0777b
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ export function EsvIntersection(props: EsvIntersectionProps): React.ReactNode {
const existingLayer = esvController.getLayer(layer.id);
if (existingLayer) {
// The last pixi layer does always hold the canvas element. If the last layer is removed and the other pixi layers are only updated,
// they don't have a canvas to draw in anymore. Hence, in order to add a new canvas, the old layer gets removed and a new one added.
// they don't have a canvas to draw to anymore. Hence, in order to add a new canvas, the old layer gets removed and a new one added.
if (isPixiLayer(existingLayer)) {
esvController.removeLayer(layer.id);
const newLayerOptions = cloneDeep(layer.options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ export class PolylineIntersectionLayer extends PixiLayer<PolylineIntersectionDat

constructor(ctx: PixiRenderApplication, id: string, options: PolylineIntersectionLayerOptions) {
super(ctx, id, options);

this.data?.colorScale.setRange(options.data?.minGridPropValue ?? 0, options.data?.maxGridPropValue ?? 1000);
}

override onRescale(event: OnRescaleEvent): void {
Expand Down
16 changes: 13 additions & 3 deletions frontend/src/lib/utils/ColorScale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ export type ColorScaleOptions = {
colorPalette: ColorPalette;
gradientType: ColorScaleGradientType;
steps: number;
min?: number;
max?: number;
divMidPoint?: number;
};

export class ColorScale {
Expand All @@ -57,11 +60,11 @@ export class ColorScale {

constructor(options: ColorScaleOptions) {
this._colorPalette = options.colorPalette;
this._min = 0;
this._max = 1;
this._min = options.min ?? 0;
this._max = options.max ?? 1;
this._type = options.type;
this._gradientType = options.gradientType;
this._divMidPoint = 0.5;
this._divMidPoint = options.divMidPoint ?? 0.5;
this._steps = options.steps;
}

Expand Down Expand Up @@ -210,6 +213,10 @@ export class ColorScale {
return plotlyColorScale;
}

getColorMap(): string[] {
return this.getPlotlyColorScale().map((color) => color[1]);
}

getAsPlotlyColorScaleMarkerObject(): PlotlyMarkerColorScaleObject {
return {
colorscale: this.getPlotlyColorScale(),
Expand Down Expand Up @@ -258,6 +265,9 @@ export class ColorScale {
colorPalette: this._colorPalette,
gradientType: this._gradientType,
steps: this._steps,
min: this._min,
max: this._max,
divMidPoint: this._divMidPoint,
});
}
}
9 changes: 6 additions & 3 deletions frontend/src/modules/3DViewer/view/utils/layers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BoundingBox3d_api, WellboreTrajectory_api } from "@api";
import { Layer } from "@deck.gl/core/typed";
import { ColorScale } from "@lib/utils/ColorScale";
import { TGrid3DColoringMode } from "@webviz/subsurface-viewer";
import { AxesLayer, Grid3DLayer, WellsLayer } from "@webviz/subsurface-viewer/dist/layers";

Expand Down Expand Up @@ -43,7 +44,8 @@ type WorkingGrid3dLayer = {
export function makeGrid3DLayer(
gridSurfaceData: GridSurface_trans,
gridParameterData: GridMappedProperty_trans,
showGridLines: boolean
showGridLines: boolean,
colorScale: ColorScale
): WorkingGrid3dLayer {
const offsetXyz = [gridSurfaceData.origin_utm_x, gridSurfaceData.origin_utm_y, 0];
const pointsNumberArray = gridSurfaceData.pointsFloat32Arr.map((val, i) => val + offsetXyz[i % 3]);
Expand All @@ -59,6 +61,7 @@ export function makeGrid3DLayer(
pickable: true,
colorMapName: "Continuous",
colorMapClampColor: true,
colorMapRange: [colorScale.getMin(), colorScale.getMax()],
/*
colorMapFunction: (value: number) => {
const interpolatedColor = colorScale.getColorPalette().getInterpolatedColor(value);
Expand All @@ -77,7 +80,7 @@ export function makeGrid3DLayer(
export function makeIntersectionLayer(
polylineIntersectionData: PolylineIntersection_trans,
showGridLines: boolean,
gridMinAndMaxPropValues: [number, number]
colorScale: ColorScale
): WorkingGrid3dLayer {
const polyData = buildVtkStylePolyDataFromFenceSections(polylineIntersectionData.fenceMeshSections);
const grid3dIntersectionLayer = new Grid3DLayer({
Expand All @@ -86,7 +89,7 @@ export function makeIntersectionLayer(
polysData: polyData.polys as unknown as number[],
propertiesData: polyData.props as unknown as number[],
colorMapName: "Continuous",
colorMapRange: gridMinAndMaxPropValues,
colorMapRange: [colorScale.getMin(), colorScale.getMax()],
colorMapClampColor: true,
coloringMode: TGrid3DColoringMode.Property,
ZIncreasingDownwards: false,
Expand Down
14 changes: 8 additions & 6 deletions frontend/src/modules/3DViewer/view/view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,23 +261,22 @@ export function View(props: ModuleViewProps<State, SettingsToViewInterface>): Re
const axesLayer = makeAxesLayer(gridModelBoundingBox3d);

const layers: Layer[] = [northArrowLayer, axesLayer];
const colorScaleClone = colorScale.clone();

if (gridSurfaceQuery.data && gridParameterQuery.data) {
const minPropValue = gridParameterQuery.data.min_grid_prop_value;
const maxPropValue = gridParameterQuery.data.max_grid_prop_value;
if (!useCustomBounds) {
colorScale.setRangeAndMidPoint(
colorScaleClone.setRangeAndMidPoint(
minPropValue,
maxPropValue,
minPropValue + (maxPropValue - minPropValue) / 2
);
}
layers.push(makeGrid3DLayer(gridSurfaceQuery.data, gridParameterQuery.data, showGridLines));
layers.push(makeGrid3DLayer(gridSurfaceQuery.data, gridParameterQuery.data, showGridLines, colorScaleClone));

if (polylineIntersectionQuery.data && showIntersection) {
layers.push(
makeIntersectionLayer(polylineIntersectionQuery.data, showGridLines, [minPropValue, maxPropValue])
);
layers.push(makeIntersectionLayer(polylineIntersectionQuery.data, showGridLines, colorScaleClone));
}
}

Expand All @@ -286,7 +285,10 @@ export function View(props: ModuleViewProps<State, SettingsToViewInterface>): Re
layers.push(makeWellsLayer(filteredFieldWellBoreTrajectories, maybeWellboreUuid));
}

const colorScaleWithName = ColorScaleWithName.fromColorScale(colorScale, gridModelParameterName ?? "Grid model");
const colorScaleWithName = ColorScaleWithName.fromColorScale(
colorScaleClone,
gridModelParameterName ?? "Grid model"
);

return (
<div className="w-full h-full">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ export function SeismicLayerSettingsComponent(props: SeismicLayerSettingsProps):
}

function handleColorScaleChange(newColorScale: ColorScale, areBoundariesUserDefined: boolean) {
props.layer.setColorScale(newColorScale);
props.layer.setUseCustomColorScaleBoundaries(areBoundariesUserDefined);
props.layer.setColorScale(newColorScale);
}

return (
Expand Down
111 changes: 104 additions & 7 deletions frontend/src/modules/Intersection/utils/layers/SeismicLayer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SeismicFenceData_api } from "@api";
import { SeismicInfo, generateSeismicSliceImage, getSeismicInfo } from "@equinor/esv-intersection";
import { SeismicInfo, findIndexOfSample, getSeismicInfo } from "@equinor/esv-intersection";
import { apiService } from "@framework/ApiService";
import { EnsembleIdent } from "@framework/EnsembleIdent";
import { defaultContinuousDivergingColorPalettes } from "@framework/utils/colorPalettes";
Expand All @@ -9,6 +9,7 @@ import { b64DecodeFloatArrayToFloat32 } from "@modules/_shared/base64";
import { ColorScaleWithName } from "@modules/_shared/utils/ColorScaleWithName";
import { QueryClient } from "@tanstack/query-core";

import { Rgb, parse } from "culori";
import { isEqual } from "lodash";

import { BaseLayer, BoundingBox, LayerStatus, LayerTopic } from "./BaseLayer";
Expand Down Expand Up @@ -252,13 +253,18 @@ export class SeismicLayer extends BaseLayer<SeismicLayerSettings, SeismicLayerDa
trajectory: trajectoryFenceProjection,
colorScale: this.getColorScale(),
};
const colormap = options.colorScale.sampleColors(options.colorScale.getNumSteps() || 10);

const image = await generateSeismicSliceImage({ ...options }, options.trajectory, colormap, {
isLeftToRight: true,
seismicMin: this.getUseCustomColorScaleBoundaries() ? options.colorScale.getMin() : undefined,
seismicMax: this.getUseCustomColorScaleBoundaries() ? options.colorScale.getMax() : undefined,
})
const useCustomColorScaleBoundaries = this.getUseCustomColorScaleBoundaries();

const image = await generateSeismicSliceImage(
{ ...options },
options.trajectory,
options.colorScale.clone(),
useCustomColorScaleBoundaries,
{
isLeftToRight: true,
}
)
.then((result) => {
return result ?? null;
})
Expand Down Expand Up @@ -417,3 +423,94 @@ export function createSeismicSliceImageYAxisValuesArrayFromFenceData(fenceData:
}
return yAxisValues;
}

export async function generateSeismicSliceImage(
data: { datapoints: number[][]; yAxisValues: number[] },
trajectory: number[][],
colorScale: ColorScale,
useCustomColorScaleBoundaries: boolean,
options: {
isLeftToRight: boolean;
seismicRange?: number;
seismicMin?: number;
seismicMax?: number;
} = { isLeftToRight: true }
): Promise<ImageBitmap | undefined> {
if (!(data && data.datapoints && data.datapoints.length > 0)) {
return undefined;
}
const { datapoints: dp } = data;

const min =
options?.seismicMin ||
options?.seismicRange ||
dp.reduce((val: number, array: number[]) => Math.min(...array, val), 0);
const max =
options?.seismicMax ||
options?.seismicRange ||
dp.reduce((val: number, array: number[]) => Math.max(...array, val), 0);

const absMax = Math.max(Math.abs(min), Math.abs(max));

const dmin = -absMax;
const dmax = absMax;

if (!useCustomColorScaleBoundaries) {
colorScale.setRange(dmin, dmax);
}

const length = trajectory[0]?.[0]! - trajectory[trajectory.length - 1]?.[0]!;

Check failure on line 462 in frontend/src/modules/Intersection/utils/layers/SeismicLayer.ts

View workflow job for this annotation

GitHub Actions / frontend

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong

Check failure on line 462 in frontend/src/modules/Intersection/utils/layers/SeismicLayer.ts

View workflow job for this annotation

GitHub Actions / frontend

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong
const width = Math.abs(Math.floor(length / 5));
const height = data.yAxisValues.length;

// Generate image
const imageDataUint8Arr = new Uint8ClampedArray(width * height * 4);

let offset = 0;

let pos = options?.isLeftToRight ? trajectory[0]?.[0]! : trajectory[trajectory.length - 1]?.[0]!;

Check failure on line 471 in frontend/src/modules/Intersection/utils/layers/SeismicLayer.ts

View workflow job for this annotation

GitHub Actions / frontend

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong

Check failure on line 471 in frontend/src/modules/Intersection/utils/layers/SeismicLayer.ts

View workflow job for this annotation

GitHub Actions / frontend

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong

const step = (length / width) * (options?.isLeftToRight ? -1 : 1);

let val1: number;
let val2: number;
let val: number;
let color: number[];
const black = [0, 0, 0];
let opacity: number;

for (let x = 0; x < width; x++) {
offset = x * 4;
const index = findIndexOfSample(trajectory, pos);
const x1 = trajectory[index]?.[0]!;

Check failure on line 485 in frontend/src/modules/Intersection/utils/layers/SeismicLayer.ts

View workflow job for this annotation

GitHub Actions / frontend

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong
const x2 = trajectory[index + 1]?.[0]!;

Check failure on line 486 in frontend/src/modules/Intersection/utils/layers/SeismicLayer.ts

View workflow job for this annotation

GitHub Actions / frontend

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong
const span = x2 - x1;
const dx = pos - x1;
const ratio = dx / span;

for (let y = 0; y < height; y++) {
val1 = dp[y]?.[index]!;

Check failure on line 492 in frontend/src/modules/Intersection/utils/layers/SeismicLayer.ts

View workflow job for this annotation

GitHub Actions / frontend

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong
val2 = dp[y]?.[index + 1]!;

Check failure on line 493 in frontend/src/modules/Intersection/utils/layers/SeismicLayer.ts

View workflow job for this annotation

GitHub Actions / frontend

Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong
color = black;
opacity = 0;
if (val1 !== null && val2 !== null) {
val = val1 * (1 - ratio) + val2 * ratio;
const hexColor = colorScale.getColorForValue(val);
const rgbColor = parse(hexColor) as Rgb | undefined;
if (rgbColor) {
color = [rgbColor.r * 255, rgbColor.g * 255, rgbColor.b * 255];
opacity = 255;
}
}

imageDataUint8Arr.set([color[0]!, color[1]!, color[2]!, opacity], offset);

offset += width * 4;
}
pos += step;
}
const imageData = new ImageData(imageDataUint8Arr, width, height);
const image = await createImageBitmap(imageData, 0, 0, width, height);

return image;
}

0 comments on commit 7c0777b

Please sign in to comment.