GF Instruments correction¶
2020-05-23
GF Instruments use a custom linear calibration to convert the quadrature values measured by their instruments (e.g. CMD Explorer and CMD Mini-Explorer) into ECa. These calibrations can be supplied from the control unit as: ‘F-Ground’, ‘F-0m’ or ‘F1-m’. It is essential to be aware of which calibration is being applied to the data as it has an important impact on the inversion of data collected with these instruments. > NOTE: this notebook reflects the best of our knowledge on the GF Instruments calibration procedure. Please consult with the manufacturer for further details.
# import
import sys
sys.path.append('../src')
from emagpy.invertHelper import fMaxwellQ, getQs
from emagpy import Problem
import numpy as np
import matplotlib.pyplot as plt
What is the purpose of the calibration?¶
The main advantage of these calibrations is that the ECa value obtained by the device is much closer to the ground EC than it would be if an ECa value obtained with the LIN approximation is used (e.g. as is done by most other EMI manufacturers). The LIN equation (\(ECa = \frac{4}{\omega \mu_0 s^2}Q\)) is a simple multiplication of the Quadrature (\(Q\)) value. When the device is at 1 m above the ground the quadrature value measured would be smaller (due to the air layer) than when on the ground. This means that for the LIN derived ECa for the case where the device is at 1 m a smaller ECa value would be obtained in comparison to the ECa from the ‘F-1m’.
How the GF calibration works?¶
These calibrations equations are based on measurements made at a site with an assumed homogeneous conductivity of 50 mS/m. The quadrature values measured over 50 mS/m (\(Q_{50}\)) are used to compute a slope to directly convert the quadrature to ECa.
The calibration equation is
with
Once established, the calibration equation can be rearranged to be used to predict ECa as a function of measured quadrature:
# synthetic simulation of GF calibration for the CMD Explorer F-1m calibration
cpos = ['vcp','vcp','vcp','hcp','hcp','hcp'] # coil orientation
cspacing = [1.48, 2.82, 4.49, 1.48, 2.82, 4.49] # coil separation [m]
hx = [1, 1, 1, 1, 1, 1] # height above the ground [m]
freq = 10000 # Hz
depths = np.array([5])
Q50 = np.imag(getQs(np.ones(2)*50, depths, cspacing, cpos, freq, hx=hx))*1000 # [ppt]
print(Q50)
[ 0.64177379 3.56440701 10.94089299 1.14377679 5.42102768 14.2368228 ]
slope = Q50/50
print('inverted slopes:', 1/slope)
fig, axs = plt.subplots(2, 3, sharex=True, figsize=(8,4))
axs = axs.flatten()
for i in range(len(cpos)):
ax = axs[i]
ax.set_title('{:s}{:.2f}'.format(cpos[i].upper(), cspacing[i]))
ax.plot(50, Q50[i], 'o')
ax.plot(np.arange(50), slope[i] * np.arange(50), '--')
if i > 2:
ax.set_xlabel('EC [mS/m]')
if i % 3 == 0:
ax.set_ylabel('Q [ppt]')
fig.tight_layout()
inverted slopes: [77.90907081 14.02757872 4.57001088 43.71482297 9.22334343 3.51201955]

Impact on the inversion¶
The GF calibrations add a non physically-based transformation on the ECa that cannot be modelled easily. By neglecting the contribution of the air layer between the instrument and the ground, the GF calibration obtains larger ECa values (closer to the ground EC) but not representative of the signal received.
One could argue that one way to avoid that is simply to invert ECa values obtained with the ‘F-1m’ calibration such as if they were taken directly on the ground (i.e. h = 0 m). However, such an approach neglects the differences in the shapes of the sensitivity functions between a device at 0 m and at 1 m above the ground. The sensitivity patterns are not the same and this can impact the inversion.
# no correction
k1 = Problem()
k1.createSurvey('../src/examples/hollin-hill/expl-transect.csv')
# with GF correction
k2 = Problem()
k2.createSurvey('../src/examples/hollin-hill/expl-transect.csv')
k2.gfCorrection(calib='F-1m')
# like if it was collected at 0 m
k3 = Problem()
k3.createSurvey('../src/examples/hollin-hill/expl-transect.csv')
k3.hx = np.zeros(6) # make it like data were collected at 0 m
ks = [k1, k2, k3]
for k in ks:
k.setInit(depths0=np.linspace(0.01, 5, 10))
k.invert(forwardModel='FSlin', method='Gauss-Newton')
# figure
fig, axs = plt.subplots(len(ks), 2, figsize=(10,7))
letters = ['a','b','c','d','e','f']
labs = ['no correction', 'GF correction', 'like if at 0 m']
for i, k in enumerate(ks):
k.show(ax=axs[i,0], vmin=5, vmax=80)
axs[i,0].get_legend().remove()
axs[i,0].set_title('({:s}) Apparent ({:s})'.format(letters[i*2], labs[i]))
k.showResults(ax=axs[i,1], vmin=5, vmax=80)
axs[i,1].set_title('({:s}) Inverted ({:s})'.format(letters[i*2+1], labs[i]))
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-1-5165e341e934> in <module>
1 # no correction
2 k1 = Problem()
----> 3 k1.createSurvey('../src/examples/hollin-hill/expl-transect.csv')
4
5 # with GF correction
/builds/hkex/emagpy/src/emagpy/Problem.py in createSurvey(self, fname, freq, hx, targetProjection, unit)
107 targetProjection = self.projection
108
--> 109 survey = Survey(fname, freq=freq, hx=hx, targetProjection=targetProjection, unit=unit)
110
111 # remove NaN from survey
/builds/hkex/emagpy/src/emagpy/Survey.py in __init__(self, fname, freq, hx, targetProjection, unit)
170 self.projection = None # store the project
171 if fname is not None:
--> 172 self.readFile(fname, targetProjection=targetProjection, unit=unit)
173 if freq is not None:
174 self.freqs = np.ones(len(self.coils))*freq
/builds/hkex/emagpy/src/emagpy/Survey.py in readFile(self, fname, sensor, targetProjection, unit)
201 if fname.find('.DAT')!=-1:
202 delimiter = '\t'
--> 203 df = pd.read_csv(fname, delimiter=delimiter)
204 self.readDF(df, name, sensor, targetProjection, unit)
205
/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, dialect, error_bad_lines, warn_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)
603 kwds.update(kwds_defaults)
604
--> 605 return _read(filepath_or_buffer, kwds)
606
607
/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in _read(filepath_or_buffer, kwds)
455
456 # Create the parser.
--> 457 parser = TextFileReader(filepath_or_buffer, **kwds)
458
459 if chunksize or iterator:
/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in __init__(self, f, engine, **kwds)
812 self.options["has_index_names"] = kwds["has_index_names"]
813
--> 814 self._engine = self._make_engine(self.engine)
815
816 def close(self):
/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in _make_engine(self, engine)
1043 )
1044 # error: Too many arguments for "ParserBase"
-> 1045 return mapping[engine](self.f, **self.options) # type: ignore[call-arg]
1046
1047 def _failover_to_python(self):
/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in __init__(self, src, **kwds)
1860
1861 # open handles
-> 1862 self._open_handles(src, kwds)
1863 assert self.handles is not None
1864 for key in ("storage_options", "encoding", "memory_map", "compression"):
/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in _open_handles(self, src, kwds)
1361 compression=kwds.get("compression", None),
1362 memory_map=kwds.get("memory_map", False),
-> 1363 storage_options=kwds.get("storage_options", None),
1364 )
1365
/usr/local/lib/python3.7/site-packages/pandas/io/common.py in get_handle(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)
645 encoding=ioargs.encoding,
646 errors=errors,
--> 647 newline="",
648 )
649 else:
FileNotFoundError: [Errno 2] No such file or directory: '../src/examples/hollin-hill/expl-transect.csv'
Observations:¶
- when quadrature values are transformed to LIN ECa values (c), the ECa values are smaller as they take into account the air layer (0 mS/m) between the instrument and the ground.
- inverting EC without correcting for the GF calibration shows larger amplitude in inverted EC values (i.e. the model conductivities are artificially elevated).
- inverting data ‘like if at 0 m’ (i.e. wrongly assuming the data were collected on the ground) provides a range of inverted EC (f) much closer to ‘GF corrected’ inverted EC (d) however their distribution is not the same as the sensitivity patterns are incorrect. Hence, the sensitivity is artificially increased closer to the surface which leads to higher EC values.
# no correction
k1 = Problem()
k1.createSurvey('../src/examples/cover-crop/coverCropTransect.csv')
k1.filterRange(vmax=40)
# with correction
k2 = Problem()
k2.createSurvey('../src/examples/cover-crop/coverCropTransect.csv')
k2.filterRange(vmax=40)
k2.gfCorrection(calib='F-0m')
ks = [k1, k2]
for k in ks:
k.setInit(depths0=np.linspace(0.01, 3, 10))
k.invert(forwardModel='CS', method='Gauss-Newton')
# figure
fig, axs = plt.subplots(len(ks), 2, figsize=(10,5))
letters = ['a','b','c','d','e','f']
labs = ['no correction', 'GF correction', 'like if at 0 m']
for i, k in enumerate(ks):
k.show(ax=axs[i,0], vmin=10, vmax=40)
axs[i,0].get_legend().remove()
axs[i,0].set_title('({:s}) Apparent ({:s})'.format(letters[i*2], labs[i]))
k.showResults(ax=axs[i,1], vmin=10, vmax=40)
axs[i,1].set_title('({:s}) Inverted ({:s})'.format(letters[i*2+1], labs[i]))
2/30 data removed (filterRange).
2/30 data removed (filterRange).
gfCorrection: F-0m calibrated ECa converted to LIN ECa
Survey 1/1
28/28 inverted
Survey 1/1
28/28 inverted

Conclusions¶
- It is important to be aware of which calibration has been used and what was the height of the instrument when data were collected
- If data are collected at 1 m above the ground, it is important to convert the calibrated ECa to a LIN ECa using ``gfCorrection(calib=’F-1m’)``. This has important impact on the inversion.
- Inverting ‘F-1m’ ECa data as if they were taken at 0 m will likely lead to wrong inversion results due to the different sensitivity pattern if the device is at 0 m or at 1 m. It is hence not recommended. > Note: we do not recommend using the ‘F-0m’ or ‘F-Ground’ calibration to collect data at 1 m above the ground as this will reduce the precision of the ECa values collected. We rather recommend to transform them after.
- If the data were collected on the ground, the GF correction can still be applied but it’s impact on the inversion will be smaller.
- EMI inversion always have some uncertainty due to the small number of coils compared to the number of parameters. However, we found out that not applying this GF correction when collecting data with ‘F-1m’ lead to substantial distortion of the inverse results, greater than the ‘normal’ uncertainty of the inversion.
Download python script: nb_gf-correction.py
Download Jupyter notebook: nb_gf-correction.ipynb
View the notebook in the Jupyter nbviewer