CamPetro

Unit Detection and Conversion

On this page

Summary

Logs arrive in different unit systems, and a calculation that assumes one system silently misreads another. This page gives the conversions between Bulk density (SI) and Bulk density, Compressional slowness (SI) and Compressional slowness, Caliper (millimetres) and caliper in inches, Neutron porosity (percent) and Neutron porosity, and Formation conductivity and resistivity, and the value-range heuristics that detect the unit when the header is blank or wrong. Use it before any curve is used in a calculation.

Inputs and outputs

Item Units
Input Bulk density (SI) kg/m³
Input Compressional slowness (SI) µs/m
Input Caliper (millimetres) mm
Input Neutron porosity (percent) pu
Input Formation conductivity mS/m
Output Bulk density g/cm³
Output Compressional slowness µs/ft
Output Caliper in
Output Neutron porosity v/v
Output True formation resistivity ohm·m

Equations

Each conversion is a fixed factor, except the conductivity, which is a reciprocal.

Density, from kg/m³ to g/cm³:

\[ \rhob = \frac{\rhobSI}{1000} \]

Compressional slowness, from µs/m to µs/ft (a foot is 0.3048 m, so a foot takes fewer microseconds than a metre):

\[ \dtc = 0.3048\,\dtSI \]

Caliper, from mm to in:

\[ d_c = \frac{\dcMM}{25.4} \]

Neutron porosity, from porosity units (percent) to a volume fraction:

\[ \phiN = \frac{\phiNpu}{100} \]

Resistivity from conductivity (mS/m is the same number as mmho/m):

\[ \Rt = \frac{1000}{\sigmaMS} \]

The factor 1000 appears because 1 S/m is 1000 mS/m, and resistivity in ohm·m is the reciprocal of conductivity in S/m.

Symbol Variable Units Typical range
\(\rho_{b,\mathrm{SI}}\) Bulk density (SI) kg/m³ 1500 to 3200
\(\rho_b\) Bulk density g/cm³ 1.8 to 3.0
\(\Delta t_{\mathrm{SI}}\) Compressional slowness (SI) µs/m 130 to 660
\(\Delta t\) Compressional slowness µs/ft 40 to 140
\(d_{c,\mathrm{mm}}\) Caliper (millimetres) mm 100 to 760
\(d_c\) Caliper in 6 to 20
\(\phi_{N,\mathrm{pu}}\) Neutron porosity (percent) pu -2 to 60
\(\phi_N\) Neutron porosity v/v -0.02 to 0.60
\(\sigma\) Formation conductivity mS/m 1 to 5000
\(R_t\) True formation resistivity ohm·m 0.2 to 2000

Single-value calculator

Behavior

The first four conversions are straight lines, so the interesting behaviour is in the conductivity. Resistivity is the reciprocal of conductivity, so it falls steeply at low conductivity and flattens at high conductivity: 10 mS/m is 100 ohm·m, 50 mS/m is 20 ohm·m, 100 mS/m is 10 ohm·m, 500 mS/m is 2 ohm·m and 1000 mS/m is 1 ohm·m. Equal steps in conductivity are therefore very unequal steps in resistivity, which is why resistivity is averaged, filtered and normalised in logarithms and conductivity is averaged linearly. At the default inputs the calculator gives 2.650 g/cm³, 67.06 µs/ft, 8.50 in, 0.120 v/v and 10.00 ohm·m.

Parameter guidance

The conversion factors are exact definitions. The practical work is deciding which conversion to apply. Read the unit string in the header first, accepting the common spellings (for example G/C3, G/CC and g/cm3 for density, US/F and USEC/FT for slowness, MMHO and MS/M for conductivity), then confirm it against the values. Apply a value heuristic only to confirm or, when the header is blank, to propose.

Curve Unit A Unit B Heuristic on non-null values
Bulk density g/cm³ (1.5 to 3.2) kg/m³ (1500 to 3200) Median above 100 means kg/m³. The gap between the ranges is very wide, so this is robust.
Compressional slowness µs/ft (about 40 to 200) µs/m (about 130 to 660) The ranges overlap between 130 and 200. A 2nd percentile below 120 means µs/ft, a 98th percentile above 250 means µs/m, anything else is ambiguous and goes to a person.
Caliper in (about 4 to 30) mm (about 100 to 760) Median above 40 means mm.
Porosity-like fractions v/v (0 to about 0.6) percent or pu (0 to about 60) 99th percentile above 1.5 means percent, at or below 1.0 means fraction. Tight, low-porosity intervals can sit entirely below 1 percent, so apply the rule to a whole well and not a short interval.
Conductivity or resistivity ohm·m mS/m (mmho/m) Value ranges overlap and cannot decide. Use the header and the mnemonic first, then check the sign of the correlation of the logarithm with the gamma ray in a clastic section: resistivity falls where gamma ray rises, conductivity rises.
Gamma ray gAPI other (counts, dose rate) Values far outside 0 to 400 gAPI are a different unit. Converting from a non-API unit needs a tool-specific calibration, so do not guess a factor.

The thresholds above are starting points and not standards. Set them from your own data. Apply the detection to the whole curve after null handling (see Null and Invalid Value Handling), because a sentinel value of -999.25 shifts every statistic. Shared choices are on the step page.

Worked example

Detect the unit of curves from their values on synthetic data, and convert a few sample values. The code is the detector used as the reference implementation of this page:

import numpy as np


def _clean(x):
    x = np.asarray(x, dtype=float)
    return x[np.isfinite(x)]


def detect_density(x):
    """Bulk density: rock is 1.5 to 3.2 g/cc, so 1500 to 3200 in kg/m3."""
    med = np.median(_clean(x))
    return "kg/m3" if med > 100 else "g/cc"


def detect_sonic(x):
    """Slowness: us/ft is 40 to 200 and us/m is about 130 to 660. Only the tails separate them."""
    v = _clean(x)
    lo, hi = np.percentile(v, 2), np.percentile(v, 98)
    if lo < 120:
        return "us/ft"      # nothing real reads below about 130 us/m
    if hi > 250:
        return "us/m"       # nothing real reads above about 200 us/ft
    return "ambiguous"


def detect_caliper(x):
    """Hole diameter: 4 to 30 in, or 100 to 760 mm."""
    return "mm" if np.median(_clean(x)) > 40 else "in"


def detect_fraction(x):
    """Porosity: fraction (0 to ~0.6) or percent (0 to ~60)."""
    p99 = np.percentile(_clean(x), 99)
    if p99 > 1.5:
        return "percent"
    return "fraction" if p99 <= 1.0 else "ambiguous"


def detect_conductivity(x, gr):
    """A resistivity falls where gamma ray rises in clastics; a conductivity rises. Weak but useful."""
    ok = np.isfinite(x) & np.isfinite(gr) & (np.asarray(x) > 0)
    r = np.corrcoef(np.log10(np.asarray(x)[ok]), np.asarray(gr)[ok])[0, 1]
    return ("conductivity" if r > 0 else "resistivity"), r


if __name__ in ("__main__", "worked_example"):
    rng = np.random.default_rng(5)
    n = 400
    gr = np.clip(70 + 45 * np.sin(np.linspace(0, 12, n)) + rng.normal(0, 6, n), 10, 200)
    rt = 10 ** (1.2 - 0.9 * (gr - 70) / 45 * 0.5 + rng.normal(0, 0.05, n))      # ohm.m, falls as GR rises
    curves = {
        "RHOB  (kg/m3 file)": (detect_density, 1000 * (2.55 + rng.normal(0, 0.05, n))),
        "RHOB  (g/cc file) ": (detect_density, 2.55 + rng.normal(0, 0.05, n)),
        "DT    (us/m file) ": (detect_sonic, (75 + 12 * np.sin(np.linspace(0, 9, n))) / 0.3048),
        "DT    (us/ft file)": (detect_sonic, 75 + 12 * np.sin(np.linspace(0, 9, n))),
        "CALI  (mm file)   ": (detect_caliper, 25.4 * (8.6 + np.abs(rng.normal(0, 0.4, n)))),
        "NPHI  (percent)   ": (detect_fraction, np.abs(rng.normal(14, 5, n))),
        "NPHI  (fraction)  ": (detect_fraction, np.abs(rng.normal(0.14, 0.05, n))),
    }
    for name, (fn, data) in curves.items():
        print(f"{name} -> {fn(data)}")
    print("sonic, narrow window 60 to 100 us/ft:", detect_sonic(np.linspace(60, 100, n)))
    print("sonic, same rock in us/m (197 to 328):", detect_sonic(np.linspace(60, 100, n) / 0.3048))
    kind, r = detect_conductivity(rt, gr)
    print(f"curve of resistivity values -> {kind} (corr with GR {r:+.2f})")
    kind, r = detect_conductivity(1000.0 / rt, gr)
    print(f"curve of conductivity values -> {kind} (corr with GR {r:+.2f})")
    print()
    print("conversions of a few values:")
    print(f"  2650 kg/m3 -> {2650 / 1000:.3f} g/cc")
    print(f"  220 us/m   -> {220 * 0.3048:.2f} us/ft")
    print(f"  216 mm     -> {216 / 25.4:.2f} in")
    print(f"  12 pu      -> {12 / 100:.2f} v/v")
    print(f"  100 mS/m   -> {1000 / 100:.1f} ohm.m")

Output

RHOB  (kg/m3 file) -> kg/m3
RHOB  (g/cc file)  -> g/cc
DT    (us/m file)  -> us/m
DT    (us/ft file) -> us/ft
CALI  (mm file)    -> mm
NPHI  (percent)    -> percent
NPHI  (fraction)   -> fraction
sonic, narrow window 60 to 100 us/ft: us/ft
sonic, same rock in us/m (197 to 328): us/m
curve of resistivity values -> resistivity (corr with GR -0.99)
curve of conductivity values -> conductivity (corr with GR +0.99)

conversions of a few values:
  2650 kg/m3 -> 2.650 g/cc
  220 us/m   -> 67.06 us/ft
  216 mm     -> 8.50 in
  12 pu      -> 0.12 v/v
  100 mS/m   -> 10.0 ohm.m

Assumptions and limitations

  • The curve has been aliased to the right family, so that the heuristic for the right quantity is applied.
  • Null values were removed before the statistics were computed. A -999.25 in a density curve gives a negative median and a wrong answer.
  • A single unit applies to the whole curve. A merged curve can have a different unit in each section, in which case detect on each run before merging.
  • The curve has enough valid samples. A handful of samples in a narrow interval can sit in the overlap zone of the ranges.
  • For the conductivity test, the section includes clastic beds with a gamma ray contrast. In clean carbonates the correlation is weak and the test is not usable.

QC checks

  • After conversion, the median and the 2nd and 98th percentiles of each curve are inside the plausible range of its family.
  • A cross-check between curves holds: density porosity, neutron porosity and sonic porosity of a clean water zone agree within a few porosity units.
  • Resistivity does not decrease with depth in a way that contradicts the geology only because of a conductivity error. Compare with the offset well.
  • The result of automatic detection agrees with the header whenever the header is present. Disagreements are listed for a person.
  • Unit labels on the converted curves were updated, so the same conversion is never applied twice.

Going Deeper

The unit systems come from different regions and eras. Imperial units are standard in North American wireline work, while SI units appear in other regions, in some LWD data and in processed products from geophysical software. Conductivity is not a regional choice: induction tools measure conductivity directly, so older logs often carry it, and it is converted to resistivity for interpretation. Because the reciprocal is nonlinear, a conductivity log that is displayed or filtered as resistivity (or the reverse) gives different values for the same operation. Automatic detection is useful for large data sets, but it is a proposal; for curves whose header and values disagree, a person with the log header in front of them is the better test.

References

  1. Canadian Well Logging Society, 1992 (revised 1999). LAS Version 2.0: A Digital Standard for Logs. Update February 1992 (CWLS Log ASCII Standard).
  2. Asquith, G. and Krygowski, D., 2004. Basic Well Log Analysis, 2nd edition. AAPG Methods in Exploration Series 16, American Association of Petroleum Geologists, Tulsa, OK.

Python reference implementation

Python reference implementation

The Python reference implementation is available to registered users with a verified email address. Register or sign in to view it.