Skip to content

Commit

Permalink
Add tvd info to picks
Browse files Browse the repository at this point in the history
  • Loading branch information
HansKallekleiv committed Sep 6, 2023
1 parent 85c13c5 commit 4b21bcf
Show file tree
Hide file tree
Showing 3 changed files with 343 additions and 2 deletions.
3 changes: 1 addition & 2 deletions frontend/src/modules/Intersection/IntersectionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,13 @@ import {
Trajectory,
WellborepathLayer,
generateSeismicSliceImage,
getPicksData,
getSeismicInfo,
getSeismicOptions,
transformFormationData,
} from "@equinor/esv-intersection";
import { UseQueryResult, useQuery } from "@tanstack/react-query";

import { generateGridSliceImage } from "./gridData";
import { getPicksData, transformFormationData } from "./picks";

export class ControllerHandler {
public controller: Controller;
Expand Down
1 change: 1 addition & 0 deletions frontend/src/modules/Intersection/gridData.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Modified from https://github.com/equinor/esv-intersection/blob/master/src/datautils/seismicimage.ts
import { SeismicCanvasDataOptions, findIndexOfSample } from "@equinor/esv-intersection";
import { clamp } from "@equinor/videx-math";

Expand Down
341 changes: 341 additions & 0 deletions frontend/src/modules/Intersection/picks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
// Modified from https://github.com/equinor/esv-intersection/blob/master/src/datautils/picks.ts

export interface Annotation {
title: string;
label: string;
color: string;
group: string;
md?: number;
tvd?:number;

pos?: [number, number];
}
type Pick = {
pickIdentifier?: string;
confidence: string | null;
depthReferencePoint: string;
md: number;
mdUnit: string;
tvd: number;
tvdMsl?:number;
};

type PickWithId = {
identifier: string;
} & Pick;

type Unit = {
identifier: string;
top: string;
base: string;
baseAge: number;
topAge: number;
colorR: number;
colorG: number;
colorB: number;
stratUnitLevel: number;
lithologyType: number;
stratUnitParent: number;
};

type UnitDto = {
unitName: string;
topSurface: string;
baseSurface: string;
ageBase: number;
ageTop: number;
color: {
r: number;
g: number;
b: number;
};
level: number;
lithType: number;
parent: number;
};

type PickAndUnit = PickWithId & UnitDto;

type PairedPickAndUnit = {
name: string;
mdEntry: number;
tvdEntry: number;
color: { r: number; g: number; b: number };
level: number;
entryPick: PickAndUnit;
mdExit: number;
tvdExit: number;
exitPick: PickAndUnit;
confidenceEntry: string;
confidenceExit: string;
from?: number;
to?: number;
};

const mapPick = (p: PickWithId, groupName: string): Annotation => ({
title: p.pickIdentifier || p.identifier,
group: groupName,

label: `md: ${p.md} ${p.mdUnit} ${p.depthReferencePoint}, tvd (msl): ${p.tvdMsl}`,
color: groupName === 'strat-picks' ? '#227' : 'rgba(0,0,0,0.8)',
md: p.md,
tvd: p.tvd,
});

function getReferencePicks(picks: PickWithId[]): Annotation[] {
if (!picks) {
return [];
}

return picks.map((p: PickWithId) => mapPick(p, 'ref-picks'));
}

function getEntryPicks(formationPicks: PairedPickAndUnit[]): Annotation[] {
if (!formationPicks) {
return [];
}

return formationPicks
.filter((d: PairedPickAndUnit) => d.entryPick.md === d.from)
.map((p: PairedPickAndUnit) => mapPick(p.entryPick, 'strat-picks'));
}

function getFilteredExitPicks(formationPicks: PairedPickAndUnit[]): Annotation[] {
if (!formationPicks) {
return [];
}

return (
formationPicks
.filter((d: PairedPickAndUnit) => formationPicks.findIndex((p: PairedPickAndUnit) => Math.abs(p.entryPick.md - d.exitPick.md) < 0.5) === -1)
.map((p: PairedPickAndUnit) => mapPick(p.exitPick, 'strat-picks'))
// Remove duplicates from unitpicks filling in gaps in formation
.filter((obj: Annotation, i: number, array: Annotation[]) => i === array.findIndex((v: Annotation) => v.title === obj.title && v.md === obj.md))
);
}

export const getPicksData = (picksData: { unitPicks: PairedPickAndUnit[]; nonUnitPicks: PickWithId[] }): Annotation[] =>
[...getReferencePicks(picksData.nonUnitPicks), ...getEntryPicks(picksData.unitPicks), ...getFilteredExitPicks(picksData.unitPicks)].sort(
(a, b) => a.md! - b.md!,
);

/**
* @param {Unit} u
*/
const unitDto = (u: Unit): UnitDto => ({
unitName: u.identifier,
topSurface: u.top,
baseSurface: u.base,
ageBase: u.baseAge,
ageTop: u.topAge,
color: {
r: u.colorR === null ? 255 : u.colorR,
g: u.colorG === null ? 255 : u.colorG,
b: u.colorB === null ? 255 : u.colorB,
},
level: u.stratUnitLevel,
lithType: u.lithologyType,
parent: u.stratUnitParent,
});

/**
*
* @param {number} from
* @param {number} to
* @param {{ from: number; to: number; itm: PairedPickAndUnit }[]} arr
* @param {number} arr.to
* @param {number} arr.from
* @returns {[number, number][]}
*/
function findGaps(from: number, to: number, arr: { from: number; to: number; itm: PairedPickAndUnit }[]): [number, number][] {
if (arr.length === 0) {
return [[from, to]];
}
const gaps: [number, number][] = [];
let d = from;
let i = 0;
while (d < to && i < arr.length) {
const itm = arr[i]!;
if (itm.from > d) {
gaps.push([d, Math.min(itm.from, to)]);
}
d = Math.min(to, Math.max(from, itm.to));
i += 1;
}
if (d < to) {
gaps.push([d, to]);
}
return gaps;
}

/**
* @param {Unit[]} units
* @returns {UnitDto[]}
*/
const transformStratColumn = (units: Unit[]): UnitDto[] => units.map(unitDto);

/**
* Join picks data with strat column units
* @param {Pick[]} picks picks
* @param {Unit[]} stratColumn strat column
*/
function joinPicksAndStratColumn(picks: Pick[], stratColumn: Unit[]): { joined: PickAndUnit[]; nonUnitPicks: PickWithId[] } {
const transformed = transformStratColumn(stratColumn);
const nonUnitPicks: PickWithId[] = [];
const joined: PickAndUnit[] = [];
picks.forEach((p: Pick) => {
const matches = transformed.filter((u: UnitDto) => p.pickIdentifier?.search(new RegExp(`(${u.topSurface}|${u.baseSurface})`, 'i')) !== -1);
if (matches.length > 0) {
matches.forEach((u: UnitDto) =>
joined.push({
md: p.md,
tvd: p.tvd,
identifier: p.pickIdentifier!,
confidence: p.confidence,
mdUnit: p.mdUnit,
depthReferencePoint: p.depthReferencePoint,
...u,
}),
);
} else {
nonUnitPicks.push({ identifier: p.pickIdentifier!, ...p });
}
});

return { joined, nonUnitPicks };
}

/**
* Find matching pairs of entry/exit picks
* @param {PickAndUnit[]} joined picks joined with strat column units
*/
function pairJoinedPicks(joined: PickAndUnit[]): PairedPickAndUnit[] {
// pair picks by unit name
const pairs = [];
let current = null;

const sorted = joined
.filter((d: PickAndUnit) => d.level)
.sort((a: PickAndUnit, b: PickAndUnit) => a.unitName.localeCompare(b.unitName) || a.md - b.md || a.ageTop - b.ageTop);

while (sorted.length > 0) {
current = sorted.shift()!;
const name = current.identifier;
let pairWithName: string;

const isTop = name === current.topSurface;
const isBase = name === current.baseSurface;

if (isTop) {
pairWithName = current.baseSurface;
} else if (isBase) {
pairWithName = current.topSurface;
} else {
console.warn(`Unable to match ${name} with top or base surface, ignored`);
continue;
}

let top: PickAndUnit | undefined;
let base: PickAndUnit | undefined;

const pairWith = sorted.find((p: PickAndUnit) => p.identifier === pairWithName);
if (!pairWith) {
console.warn(`Unable to find ${pairWithName} pick for ${name}`);
if (isTop) {
top = current;
base = joined
.filter((d: PickAndUnit) => d.level)
.sort((a: PickAndUnit, b: PickAndUnit) => a.md - b.md)
.find((p: PickAndUnit) => p.md > top!.md);
if (base) {
console.warn(`Using ${base.identifier} as base for ${name}`);
} else {
console.warn(`Unable to find a base pick for ${name} pick at ${top.md}, ignored`);
continue;
}
} else if (isBase) {
base = current;
top = joined
.filter((d: PickAndUnit) => d.level)
.sort((a: PickAndUnit, b: PickAndUnit) => b.md - a.md)
.find((p: PickAndUnit) => p.md < base!.md);
if (top) {
console.warn(`Using ${top.identifier} as top for ${name}`);
} else {
console.warn(`Unable to find a top pick for ${name} pick at ${base.md}, ignored`);
continue;
}
} else {
console.warn(`${name} ignored`);
continue;
}
} else {
top = isTop ? current : pairWith;
base = isTop ? pairWith : current;

if (top.md > base.md) {
[top, base] = [base, top];
}

sorted.splice(sorted.indexOf(pairWith), 1);
}

pairs.push(<PairedPickAndUnit>{
name: top.unitName,
mdEntry: top.md,
tvdEntry: top.tvd,
color: top.color,
level: top.level,
entryPick: top,
mdExit: base.md,
tvdExit: base.tvd,
exitPick: base,
confidenceEntry: top.confidence,
confidenceExit: base.confidence,
});
}

return pairs;
}

/**
* Transform data for formation track
* @param {Pick[]} picks picks
* @param {Unit[]} stratColumn strat column
*/
export function transformFormationData(picks: Pick[], stratColumn: Unit[]): { unitPicks: PairedPickAndUnit[]; nonUnitPicks: PickWithId[] } {
const { joined, nonUnitPicks } = joinPicksAndStratColumn(picks, stratColumn);
const pairs = pairJoinedPicks(joined);

const itemstack = pairs
.filter((d: PairedPickAndUnit) => d.mdEntry < d.mdExit)
.sort((a, b) => a.mdEntry - b.mdEntry || a.level - b.level)
.reverse();

// flatten groups of unit picks, so that the highest level is
// given presedence over lower levels for overlapping picks.
const unitPicks = [];
while (itemstack.length > 0) {
const first = itemstack.pop()!;
const group: PairedPickAndUnit[] = [];
while (itemstack.length > 0 && itemstack[itemstack.length - 1]?.level! > first.level) {

Check failure on line 321 in frontend/src/modules/Intersection/picks.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 321 in frontend/src/modules/Intersection/picks.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
group.push(itemstack.pop()!);
}
group.reverse();
group.push(first);
const arr: { from: number; to: number; itm: PairedPickAndUnit }[] = [];
group.forEach((itm: PairedPickAndUnit) => {
const gaps = findGaps(itm.mdEntry, itm.mdExit, arr);
arr.push(...gaps.map((g) => ({ from: g[0], to: g[1], itm })));
});
arr.sort((a, b) => a.from - b.from);
unitPicks.push(
...arr.map((d) => ({
from: d.from,
to: d.to,
...d.itm,
})),
);
}
return { unitPicks, nonUnitPicks };
}

0 comments on commit 4b21bcf

Please sign in to comment.