o
odinpkg.dev
packages / tool / las-io-din

las-io-din

4a82f72tool

lasiodin - LAS file IO toolkit for petrophysical data written in Odin

Apache-2.0 · updated 6 months ago

lasiodin

LAS file IO toolkit for petrophysical data written in Odin

Features, or overview (I guess)

  • Parse LAS Format 2.0 with wrap flag = false

  • Parse LAS Format 2.0 with wrap flag = true

  • Parse LAS Format 3.0 (I'll save it for the future)

  • Convert LAS to CSV

  • Convert LAS to JSON

  • lasiodin.load_las returns

    1. one big struct, LasData (see las_item.odin), and
    2. a parse error, ReadFileError union, see lasio.odin
  • LasData contains all possible section defined by LAS 2.0 specification which are 1) version information, 2) well information, 3) curve information, 4) parameter information, 5) other information, and 6) log data.

Underlying data structure that I used here are very straight forward as the LAS 2.0 specification. The primitive types of LAS in this matter are mnemonic, unit, value, and description which is underlying the basis structure for version information, well information, curve information, and parameter information. Mnemonic, unit, value, and description modeled through HeaderItem struct. Other information and log data in other hand have their own format, respectively, array of string ([]string) and map of int to array of float, (map[int][]f64).

Curve information struct shares the exact same keys as the log data map indicating that curve information also has a field, named curves which is map of int to HeaderItems, hence, we can access both of each log data and their corresponding curve information using array indexing. This design api might change overtime, considering the current memory layout of log data is not as optimized and flexible as if it were flat array in which one can modify its strides and access pattern down to each log sample. Though, current implementation can be easily applied to LAS 3.0 where the log data can holds non numerical data sample, hence converting it to flat array would be quite tricky and tedious to handle.

Usages

Other examples can be found in examples/

Load LAS data in Odin

package usage

import "core:fmt"
// here I already make a symlink in odin's shared directory to lasioodin/lasioodin
import ls "shared:lasiodin"

main :: proc()
{
    las_file, parsed_ok := ls.load_las(
        "./assets/example_1_canadian_well_logging_society.las",
        allocator=context.temp_allocator,
    )
    defer ls.delete_las_data(&las_file)
	if parsed_ok != nil { fmt.printfln("Failed to parse the data, err: %v", parsed_ok) }

    log_data := &las_file.log_data
    n_rows   := log_data.nrows
    n_curves := cast(int)log_data.ncurves
    fmt.printfln("File Name: %v", las_file.file_name)
    fmt.printfln("Log Curves: %v", las_file.other_info.len)
    fmt.printfln("\tWRAP MODE: %v", log_data.wrap)
    fmt.printfln("\tNROWS:     %v", n_rows)
    fmt.printfln("\tNCOLS:     %v", n_curves)
    for idx : int = 0; idx < n_curves; idx += 1 {
        fmt.printfln(
            "\tLOG[%v] %v \t==> %v",
            idx,
            n_rows,
            log_data.logs[idx]
        )
    }

    fmt.printfln("LasData:\n%#v", las_file)

}

will print out following:

File Name: ./assets/example_1_canadian_well_logging_society.las
Log Curves: 2
        WRAP MODE: false
        NROWS:     3
        NCOLS:     8
        LOG[0] 3        ==> [1670, 1669.875, 1669.75]
        LOG[1] 3        ==> [123.45, 123.45, 123.45]
        LOG[2] 3        ==> [2550, 2550, 2550]
        LOG[3] 3        ==> [0.45, 0.45, 0.45]
        LOG[4] 3        ==> [123.45, 123.45, 123.45]
        LOG[5] 3        ==> [123.45, 123.45, 123.45]
        LOG[6] 3        ==> [110.2, 110.2, 110.2]
        LOG[7] 3        ==> [5.5999999999999996, 5.5999999999999996, 105.59999999999999]
Expand me to see more output
LasData{
        file_name = "./assets/example_1_canadian_well_logging_society.las",
        version = Version{
                vers = HeaderItem{
                        mnemonic = "VERS",
                        unit = "",
                        value = 2,
                        descr = "CWLS LOG ASCII STANDARD -VERSION 2.0",
                },
                wrap = HeaderItem{
                        mnemonic = "WRAP",
                        unit = "",
                        value = false,
                        descr = "ONE LINE PER DEPTH STEP",
                },
                add = [],
        },
        well_info = WellInformation{
                len = 12,
                items = map[
                        5 = HeaderItem{
                                mnemonic = "FLD",
                                unit = "",
                                value = "WILDCAT",
                                descr = "FIELD",
                        },
                        4 = HeaderItem{
                                mnemonic = "WELL",
                                unit = "",
                                value = "ANY ET AL 12-34-12-34",
                                descr = "WELL",
                        },
                        7 = HeaderItem{
                                mnemonic = "PROV",
                                unit = "",
                                value = "ALBERTA",
                                descr = "PROVINCE",
                        },
                        6 = HeaderItem{
                                mnemonic = "LOC",
                                unit = "",
                                value = "12-34-12-34W5M",
                                descr = "LOCATION",
                        },
                        1 = HeaderItem{
                                mnemonic = "STOP",
                                unit = "M",
                                value = 1669.75,
                                descr = "STOP DEPTH",
                        },
                        0 = HeaderItem{
                                mnemonic = "STRT",
                                unit = "M",
                                value = 1670,
                                descr = "START DEPTH",
                        },
                        3 = HeaderItem{
                                mnemonic = "COMP",
                                unit = "",
                                value = "ANY OIL COMPANY INC.",
                                descr = "COMPANY",
                        },
                        2 = HeaderItem{
                                mnemonic = "STEP",
                                unit = "M",
                                value = -0.125,
                                descr = "STEP",
                        },
                        9 = HeaderItem{
                                mnemonic = "DATE",
                                unit = "",
                                value = "13-DEC-86",
                                descr = "LOG DATE",
                        },
                        8 = HeaderItem{
                                mnemonic = "SRVC",
                                unit = "",
                                value = "ANY LOGGING COMPANY INC.",
                                descr = "SERVICE COMPANY",
                        },
                        11 = HeaderItem{
                                mnemonic = "LIC",
                                unit = "",
                                value = 23412,
                                descr = "ERCB LICENCE NUMB",
                        },
                        10 = HeaderItem{
                                mnemonic = "UWI",
                                unit = "",
                                value = "100123401234W500",
                                descr = "UNIQUE WELL ID",
                        },
                ],
                null = HeaderItem{
                        mnemonic = "NULL",
                        unit = "",
                        value = -999.25,
                        descr = "NULL VALUE",
                },
        },
        curve_info = CurveInformation{
                len = 8,
                curves = map[
                        3 = HeaderItem{
                                mnemonic = "NPHI",
                                unit = "V/V",
                                value = "42 890 00 00",
                                descr = "4 NEUTRON POROSITY",
                        },
                        2 = HeaderItem{
                                mnemonic = "RHOB",
                                unit = "K/M3",
                                value = "45 350 01 00",
                                descr = "3 BULK DENSITY",
                        },
                        1 = HeaderItem{
                                mnemonic = "DT",
                                unit = "US/M",
                                value = "60 520 32 00",
                                descr = "2 SONIC TRANSIT TIME",
                        },
                        0 = HeaderItem{
                                mnemonic = "DEPT",
                                unit = "M",
                                value = "",
                                descr = "1 DEPTH",
                        },
                        7 = HeaderItem{
                                mnemonic = "ILD",
                                unit = "OHMM",
                                value = "07 120 46 00",
                                descr = "8 DEEP RESISTIVITY",
                        },
                        6 = HeaderItem{
                                mnemonic = "ILM",
                                unit = "OHMM",
                                value = "07 120 44 00",
                                descr = "7 MEDIUM RESISTIVITY",
                        },
                        5 = HeaderItem{
                                mnemonic = "SFLA",
                                unit = "OHMM",
                                value = "07 222 01 00",
                                descr = "6 SHALLOW RESISTIVITY",
                        },
                        4 = HeaderItem{
                                mnemonic = "SFLU",
                                unit = "OHMM",
                                value = "07 220 04 00",
                                descr = "5 SHALLOW RESISTIVITY",
                        },
                ],
        },
        parameter_info = ParameterInformation{
                len = 7,
                params = [
                        HeaderItem{
                                mnemonic = "MUD",
                                unit = "",
                                value = "GEL CHEM",
                                descr = "MUD TYPE",
                        },
                        HeaderItem{
                                mnemonic = "BHT",
                                unit = "DEGC",
                                value = 35.5,
                                descr = "BOTTOM HOLE TEMPERATURE",
                        },
                        HeaderItem{
                                mnemonic = "CSGL",
                                unit = "M",
                                value = 124.59999999999999,
                                descr = "BASE OF CASING",
                        },
                        HeaderItem{
                                mnemonic = "MATR",
                                unit = "",
                                value = "SAND",
                                descr = "NEUTRON MATRIX",
                        },
                        HeaderItem{
                                mnemonic = "MDEN",
                                unit = "",
                                value = 2710,
                                descr = "LOGGING MATRIX DENSITY",
                        },
                        HeaderItem{
                                mnemonic = "RMF",
                                unit = "OHMM",
                                value = 0.216,
                                descr = "MUD FILTRATE RESISTIVITY",
                        },
                        HeaderItem{
                                mnemonic = "DFD",
                                unit = "K/M3",
                                value = 1525,
                                descr = "DRILL FLUID DENSITY",
                        },
                ],
        },
        other_info = OtherInformation{
                len = 2,
                info = [
                        " Note: The logging tools became stuck at 625 metres causing the",
                        "data between 625 metres and 615 metres to be invalid.",
                ],
        },
        log_data = LogData{
                wrap = false,
                nrows = 3,
                ncurves = 8,
                logs = map[
                        0 = [
                                1670,
                                1669.875,
                                1669.75,
                        ],
                        1 = [
                                123.45,
                                123.45,
                                123.45,
                        ],
                        2 = [
                                2550,
                                2550,
                                2550,
                        ],
                        3 = [
                                0.45,
                                0.45,
                                0.45,
                        ],
                        4 = [
                                123.45,
                                123.45,
                                123.45,
                        ],
                        5 = [
                                123.45,
                                123.45,
                                123.45,
                        ],
                        6 = [
                                110.2,
                                110.2,
                                110.2,
                        ],
                        7 = [
                                5.5999999999999996,
                                5.5999999999999996,
                                105.59999999999999,
                        ],
                ],
        },
}

Convert LAS Data

There will be 2 targets of conversion I have planned to implement

  1. LAS to CSV (done, need test)
  2. LAS to JSON (not done)

The interface for both cases will be as follow:

// in lasiodin/converters/converters.odin
convert_las :: proc(
	out_path: string,
	config: Converter_Configuration,
	las_data: ^ls.LasData,
	flag: Converter_Target_Flags,
	allocator := context.allocator,
	loc := #caller_location,
) -> ( ok: Convert_Error,)

inputs:

  • out_path: string, output path to the string
  • config: Converter_Configuration, configuration for conversion, user need to specify the necesary configuration based on the target, e.g. delimiter and line separator if conversion target to CSV.
  • las_data: ^lasiodin.LasData, pointer to LasData struct
  • flag: Converter_Target_Flags, either .CSV or .JSON

output:

  • it would be nil if success, otherwise, Error union tag of Convert_Error

Convert To CSV

package usage

// ./examples/las_to_csv

import "core:os"
import "core:fmt"
import ls "../../lasiodin"
import conv "../../lasiodin/converters"

main :: proc() {
	if !(len(os.args) >= 3) {
		fmt.printf("Require 2 file input!")
		fmt.printf("\t input 1: path to las file")
		fmt.printf("\t input 2: output path")
		fmt.print("\n")
		return
	}

	file_name: string = os.args[1]
	las_file, parsed_ok := ls.load_las(file_name, allocator = context.allocator)
	defer ls.delete_las_data(&las_file, allocator = context.allocator)
	if parsed_ok != nil {fmt.printfln("Failed to parse the data, err: %v", parsed_ok)}

	out_name: string = os.args[2]
	ok_conv := conv.convert_las(
		out_name,
		{delimiter = string(","), line_separator = string("\n")},
		&las_file,
		.CSV,
		allocator = context.allocator,
	)
	if ok_conv != nil {fmt.printfln("Failed to convert the data to CSV, err: %v", ok_conv)}

}

build and use it:

odin build ./examples/las_to_csv -out:./bin/las_to_csv.exe -o:speed
./bin/las_to_csv.exe ./assets/example_1_canadian_well_logging_society.las ./assets/example_1_canadian_well_logging_society.csv

Convert To JSON

package usage

// ./examples/las_to_csv

import "core:os"
import "core:fmt"
import "core:encoding/json"
import ls "../../lasiodin"
import conv "../../lasiodin/converters"

main :: proc() {
	if !(len(os.args) >= 3) {
		fmt.eprintln("Require 2 files input!")
		fmt.eprintln("\t input 1: path to las file")
		fmt.eprintln("\t input 2: output path")
		return
	}

	file_name: string = os.args[1]
	las_file, parsed_ok := ls.load_las(file_name, allocator = context.allocator)
	if parsed_ok != nil {fmt.eprintfln("Failed to parse the data, err: %v", parsed_ok)}
	defer ls.delete_las_data(&las_file, allocator = context.allocator)

	out_name: string = os.args[2]
	ok_conv := conv.convert_las(
		out_name,
		{
			json_marshal_options = json.Marshal_Options {
				// Adds indentation etc
				pretty         = true,
				use_spaces     = true,
				// Output enum member names instead of numeric value.
				use_enum_names = true,
				indentation    = 0,
			},
		},
		&las_file,
		.JSON,
		allocator = context.allocator,
	)
	if ok_conv != nil {fmt.eprintfln("Failed to convert the data to JSON, err: %v", ok_conv)}

}

build and use it:

odin build ./examples/las_to_json -out:./bin/las_to_json.exe -o:speed
./bin/las_to_json.exe ./assets/example_1_canadian_well_logging_society.las ./assets/example_1_canadian_well_logging_society.json