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

Implement Well-log viewer module MVP #794

Merged
merged 16 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
7 changes: 7 additions & 0 deletions backend_py/primary/primary/services/ssdl_access/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,11 @@ class WellboreLogCurveData(BaseModel):
no_data_value: float | None
unit: str
curve_unit_desc: str | None

# This field has weird casing. This is just how SSDL has decided to return this object, so we leave it as is to make model validation
DataPoints: list[list[float | None]]
rubenthoms marked this conversation as resolved.
Show resolved Hide resolved

@property
def data_points(self) -> list[list[float | None]]:
# Utility property to "DataPoint" with proper casing
return self.DataPoints
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"@tanstack/react-query-devtools": "^5.4.2",
"@types/geojson": "^7946.0.14",
"@webviz/group-tree-plot": "^1.1.14",
"@webviz/subsurface-viewer": "^0.25.2",
"@webviz/well-log-viewer": "^1.12.7",
"@webviz/subsurface-viewer": "^1.1.1",
"@webviz/well-completions-plot": "^1.5.11",
"animate.css": "^4.1.1",
"axios": "^1.6.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/**
* Util method to do an immutable item move in an array
* Util method for moving items in an array, by index number. Does not mutate the original array.
* @param array The array to move items in
* @param from The index of the first item being moved
* @param to The index the item(s) should be moved to
* @param moveAmt The amount of items (from the start-index) that should be moved
* @returns A copy of the original array, with it's items moved accordingly
* @returns A shallow copy of the original array, with its items moved accordingly
*/
export function arrayMove<t>(array: t[], from: number, to: number, moveAmt = 1): t[] {
export function arrayMove<T>(array: T[], from: number, to: number, moveAmt = 1): T[] {
const newArrray = [...array];
const movedItems = newArrray.splice(from, moveAmt);

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/modules/WellLogViewer/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { TemplateTrack } from "@webviz/well-log-viewer/dist/components/WellLogTe
import {
allSelectedWellLogCurvesAtom,
selectedFieldIdentifierAtom,
selectedWellboreAtom,
selectedWellboreHeaderAtom,
selectedWellborePicksAtom,
wellLogTemplateTracks,
} from "./settings/atoms/derivedAtoms";
Expand All @@ -28,7 +28,7 @@ export type SettingsToViewInterface = {

export const settingsToViewInterfaceInitialization: InterfaceInitialization<SettingsToViewInterface> = {
selectedField: (get) => get(selectedFieldIdentifierAtom),
wellboreHeader: (get) => get(selectedWellboreAtom),
wellboreHeader: (get) => get(selectedWellboreHeaderAtom),
templateTracks: (get) => get(wellLogTemplateTracks),
requiredDataCurves: (get) => get(allSelectedWellLogCurvesAtom),
viewerHorizontal: (get) => get(viewerHorizontalAtom),
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/modules/WellLogViewer/loadModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { ModuleRegistry } from "@framework/ModuleRegistry";
import { InterfaceTypes, settingsToViewInterfaceInitialization } from "./interfaces";
import { MODULE_NAME } from "./registerModule";
import { Settings } from "./settings/settings";
import { settingsToViewInterfaceEffects } from "./view/atoms/interfaceEffects";
import { View } from "./view/view";

const module = ModuleRegistry.initModule<InterfaceTypes>(MODULE_NAME, {
settingsToViewInterfaceInitialization,
settingsToViewInterfaceEffects,
});

module.viewFC = View;
Expand Down
66 changes: 66 additions & 0 deletions frontend/src/modules/WellLogViewer/preview.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions frontend/src/modules/WellLogViewer/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { DrawPreviewFunc } from "@framework/Preview";

import previewImg from "./preview.svg";

export const preview: DrawPreviewFunc = function (width: number, height: number) {
return <img src={previewImg} style={{ width, height }} />;
};
3 changes: 2 additions & 1 deletion frontend/src/modules/WellLogViewer/registerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ import { ModuleRegistry } from "@framework/ModuleRegistry";
import { SyncSettingKey } from "@framework/SyncSettings";

import { InterfaceTypes } from "./interfaces";
import { preview } from "./preview";
import { clearStorageForInstance } from "./settings/atoms/persistedAtoms";

export const MODULE_NAME = "WellLogViewer";
const MODULE_TITLE = "Well log Viewer";
// TODO: Better description
const MODULE_DESCRIPTION = "Well log Viewer";
// TODO: preview Icon

ModuleRegistry.registerModule<InterfaceTypes>({
moduleName: MODULE_NAME,
defaultTitle: MODULE_TITLE,
description: MODULE_DESCRIPTION,
preview,

category: ModuleCategory.MAIN,
devState: ModuleDevState.DEV,
Expand Down
58 changes: 34 additions & 24 deletions frontend/src/modules/WellLogViewer/settings/atoms/derivedAtoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { WellPicksLayerData } from "@modules/Intersection/utils/layers/Wellpicks
import { TemplatePlot, TemplateTrack } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes";

import { atom } from "jotai";
import _, { Dictionary } from "lodash";
import _ from "lodash";

import {
userSelectedFieldIdentifierAtom,
Expand All @@ -19,28 +19,32 @@ import {
wellborePicksAndStratigraphyQueryAtom,
} from "./queryAtoms";

export const selectedEnsembleSetAtom = atom((get) => {
const ensembleSetArr = get(EnsembleSetAtom).getEnsembleArr();
export const firstEnsembleInSelectedFieldAtom = atom((get) => {
const selectedFieldId = get(userSelectedFieldIdentifierAtom);
const ensembleSetArr = get(EnsembleSetAtom).getEnsembleArr();

if (ensembleSetArr.length < 1) {
if (!ensembleSetArr.length) {
return null;
} else {
const selectedEnsemble = ensembleSetArr.find((e) => e.getFieldIdentifier() === selectedFieldId);

return selectedEnsemble ?? ensembleSetArr[0];
}

const selectedEnsemble = ensembleSetArr.find((e) => e.getFieldIdentifier() === selectedFieldId);

return selectedEnsemble ?? ensembleSetArr[0];
});

export const selectedFieldIdentifierAtom = atom((get) => {
return get(selectedEnsembleSetAtom)?.getFieldIdentifier() ?? null;
return get(firstEnsembleInSelectedFieldAtom)?.getFieldIdentifier() ?? null;
});

export const selectedWellboreAtom = atom<WellboreHeader_api | null>((get) => {
export const selectedWellboreHeaderAtom = atom<WellboreHeader_api | null>((get) => {
const availableWellboreHeaders = get(drilledWellboreHeadersQueryAtom)?.data;
const selectedWellboreId = get(userSelectedWellboreUuidAtom);

return getSelectedWellboreHeader(selectedWellboreId, availableWellboreHeaders);
if (!availableWellboreHeaders?.length) {
return null;
}

return availableWellboreHeaders.find((wh) => wh.wellboreUuid === selectedWellboreId) ?? availableWellboreHeaders[0];
});

export const selectedWellborePicksAtom = atom<WellPicksLayerData>((get) => {
Expand All @@ -59,7 +63,7 @@ export const selectedWellborePicksAtom = atom<WellPicksLayerData>((get) => {
}
});

export const groupedCurveHeadersAtom = atom<Dictionary<WellboreLogCurveHeader_api[]>>((get) => {
export const groupedCurveHeadersAtom = atom<Record<string, WellboreLogCurveHeader_api[]>>((get) => {
const logCurveHeaders = get(wellLogCurveHeadersQueryAtom)?.data ?? [];

return _.groupBy(logCurveHeaders, "logName");
Expand Down Expand Up @@ -91,17 +95,23 @@ export const allSelectedWellLogCurvesAtom = atom<string[]>((get) => {
return curveNames;
});

function getSelectedWellboreHeader(
currentId: string | null,
wellboreHeaderSet: WellboreHeader_api[] | null | undefined
): WellboreHeader_api | null {
if (!wellboreHeaderSet || wellboreHeaderSet.length < 1) {
return null;
}
/**
* Returns a list of all user-selected curves that have no available curve-header
*/
export const missingCurvesAtom = atom<string[]>((get) => {
const allSelectedWellLogCurves = get(allSelectedWellLogCurvesAtom);
const curveHeadersQuery = get(wellLogCurveHeadersQueryAtom);

if (!currentId) {
return wellboreHeaderSet[0];
}
// While loading, assume all curves are "available" (since they *most likely* are)
if (!curveHeadersQuery.data) return [];

const missingNames: string[] = [];

return wellboreHeaderSet.find((wh) => wh.wellboreUuid === currentId) ?? wellboreHeaderSet[0];
}
allSelectedWellLogCurves.forEach((selectedName) => {
if (!curveHeadersQuery.data.some(({ curveName }) => curveName === selectedName)) {
missingNames.push(selectedName);
}
});

return missingNames;
});
Original file line number Diff line number Diff line change
@@ -1,27 +1,9 @@
import { TemplateTrackConfig } from "@modules/WellLogViewer/types";
import { atomWithModuleInstanceStorage, clearModuleInstanceStorage } from "@modules/WellLogViewer/utils/atoms";
import { TemplatePlot, TemplateTrack } from "@webviz/well-log-viewer/dist/components/WellLogTemplateTypes";

import { Getter, Setter, atom } from "jotai";
import { Dictionary } from "lodash";

/**
* Extension of the template track type with additional fields used while editing
*/
export type TemplatePlotConfig = Partial<TemplatePlot> & {
// Used for state updates
_id: string;
// Wether the config has all required fields for it's curve-type
_isValid: boolean;
// This is used as the value for dropdowns. Even if the curvename is supposed to be unique, In some rare cases, the curvename is duplicated across different well-logs.
_logAndName: `${string}::${string}`;
_logAndName2?: `${string}::${string}`;
};
export type TemplateTrackConfig = Omit<TemplateTrack, "plots"> & {
// ID used to allow the settings-menu to drag-sort them
_id: string;
plots: TemplatePlotConfig[];
};

const STORAGE_KEY = "moduleSettings";
const moduleSettingsAtom = atomWithModuleInstanceStorage<Dictionary<any>>(STORAGE_KEY, {});

Expand Down
20 changes: 12 additions & 8 deletions frontend/src/modules/WellLogViewer/settings/atoms/queryAtoms.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { WellborePicksAndStratigraphicUnits_api } from "@api";
import { transformFormationData } from "@equinor/esv-intersection";
import { apiService } from "@framework/ApiService";
import { WellPicksLayerData } from "@modules/Intersection/utils/layers/WellpicksLayer";

import { atomWithQuery } from "jotai-tanstack-query";
import _ from "lodash";

import { selectedEnsembleSetAtom, selectedFieldIdentifierAtom, selectedWellboreAtom } from "./derivedAtoms";
import {
firstEnsembleInSelectedFieldAtom,
selectedFieldIdentifierAtom,
selectedWellboreHeaderAtom,
} from "./derivedAtoms";

const STALE_TIME = 60 * 1000;
const CACHE_TIME = 60 * 1000;
Expand All @@ -31,7 +36,7 @@ export const drilledWellboreHeadersQueryAtom = atomWithQuery((get) => {

*/
export const wellLogCurveHeadersQueryAtom = atomWithQuery((get) => {
const wellboreId = get(selectedWellboreAtom)?.wellboreUuid;
const wellboreId = get(selectedWellboreHeaderAtom)?.wellboreUuid;

return {
queryKey: ["getWellboreLogCurveHeaders", wellboreId],
Expand All @@ -41,18 +46,17 @@ export const wellLogCurveHeadersQueryAtom = atomWithQuery((get) => {
};
});

export const wellborePicksAndStratigraphyQueryAtom = atomWithQuery<WellPicksLayerData>((get) => {
const selectedEnsemble = get(selectedEnsembleSetAtom);
export const wellborePicksAndStratigraphyQueryAtom = atomWithQuery((get) => {
const selectedEnsemble = get(firstEnsembleInSelectedFieldAtom);

const wellboreId = get(selectedWellboreAtom)?.wellboreUuid ?? "";
const wellboreId = get(selectedWellboreHeaderAtom)?.wellboreUuid ?? "";
const caseId = selectedEnsemble?.getIdent()?.getCaseUuid() ?? "";

return {
queryKey: ["getWellborePicksAndStratigraphicUnits", wellboreId, caseId],
enabled: Boolean(caseId && wellboreId),
queryFn: async () => {
const data = await apiService.well.getWellborePicksAndStratigraphicUnits(caseId, wellboreId);

queryFn: () => apiService.well.getWellborePicksAndStratigraphicUnits(caseId, wellboreId),
select(data: WellborePicksAndStratigraphicUnits_api): WellPicksLayerData {
const transformedData = transformFormationData(data.wellbore_picks, data.stratigraphic_units as any);

// ! Sometimes the transformation data returns duplicate entries, filtering them out
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TemplateTrackSettings } from "./templateTrackSettings";
Loading
Loading