14.4. Instrument Metadata#
InstrumentMeta describes the acquisition system that recorded
a station – the data logger, its sensors, and the acquisition
software – in a form that round-trips to and from EDI >HEAD
fields as well as JSON/YAML. SensorSpec describes one sensor
channel (electrode pair or magnetic coil) within that system. Because
the same handful of commercial systems – Phoenix, Metronix, LEMI,
Zonge – recur across most MT/AMT/CSAMT surveys, KNOWN_SYSTEMS
ships ready-made presets accessible through known_system() and
list_presets(), so a station’s instrument metadata does not need
to be typed out by hand for a standard configuration.
Unlike SiteMeta or
ProvenanceMeta,
InstrumentMeta and SensorSpec are plain dataclasses – they do
not inherit PyCSAMTObject, so their
compact display comes from a hand-written __repr__/__str__
rather than the shared field-capping convention documented in
Site and Survey Metadata.
14.4.1. Sensor Coverage#
A SensorSpec records the transducer type, model, and rated
frequency band. covers() is a direct, practical use
of that band: does this sensor’s rating actually span the frequencies
a survey needs?
>>> from pycsamt.metadata.instrument import SensorSpec
>>> coil = SensorSpec("induction_coil", "MTC-150H", (1e-4, 2e3))
>>> print(coil)
<SensorSpec 'MTC-150H' [0.0001–2e+03 Hz]>
The Gabbs Valley survey documented in Provenance and Bibliography reports good data quality over roughly 0.0005-770 Hz. This coil’s rated band comfortably covers that range, but not an arbitrarily low frequency:
>>> print(coil.covers(0.0005), coil.covers(770.0))
True True
>>> print(coil.covers(0.00001))
False
A frequency_range of None means “unrated/unspecified”, which
covers() treats as universal coverage (always
True) rather than as “unknown, therefore fails” – appropriate for
an electrode, which has no meaningful frequency cutoff of its own, but
worth remembering if a magnetic sensor’s range is left unset by
mistake: an omitted rating silently stops acting as a check at all.
14.4.2. Built-In Presets#
KNOWN_SYSTEMS currently covers eight common field systems.
list_presets() returns their keys, and known_system()
builds a ready-made InstrumentMeta from one:
>>> from pycsamt.metadata.instrument import known_system, list_presets
>>> print(list_presets())
['generic_fluxgate', 'geometrics_stratagem', 'lemi_424', 'metronix_adu07', 'metronix_adu08', 'phoenix_mtx', 'phoenix_v8', 'zonge_gdp32']
>>> phoenix = known_system("phoenix_v8")
>>> print(phoenix.summary())
System : Phoenix V8
Serial : —
Software : SSMT2000
Mag. sensor : <SensorSpec 'MTC-150H' [0.0001–2e+03 Hz]>
Elec. sensor : <SensorSpec 'Pb-PbCl2 porous pot' [full band]>
Notes : Standard Phoenix V8 MT/AMT system
>>> try:
... known_system("not_a_real_system")
... except KeyError as exc:
... print(type(exc).__name__, exc)
KeyError "Unknown preset 'not_a_real_system'. Available: generic_fluxgate, geometrics_stratagem, lemi_424, metronix_adu07, metronix_adu08, phoenix_mtx, phoenix_v8, zonge_gdp32"
known_system is a thin shortcut for
InstrumentMeta.from_preset(); the preset key is normalized
(spaces and hyphens both become underscores, matched case-insensitively)
before the lookup, so "Phoenix V8" and "phoenix-v8" resolve the
same way as "phoenix_v8".
Presets are a starting point, not a claim about any specific station.
Neither of the real files used elsewhere in this section names its
acquisition hardware in a way pycsamt can recognize automatically –
see the ACQBY heuristic below – so treat a preset as “the closest
documented system,” to be confirmed against the field log, and set
serial explicitly once the actual unit is known:
>>> from pycsamt.metadata.instrument import InstrumentMeta
>>> inst = known_system("phoenix_v8")
>>> inst.serial = "V8-20473"
>>> print(inst.label)
Phoenix V8 / V8-20473
14.4.3. EDI >HEAD Round-Trip#
to_head_fields() maps system/serial to
acqby and software_version to progvers – falling back to
the running pycsamt version when no software version was recorded, so
the field is never left blank by omission:
>>> print(inst.to_head_fields())
{'acqby': 'Phoenix V8 / V8-20473', 'progvers': 'pyCSAMT 2.3.1'}
The reverse direction, from_head(), is a
best-effort heuristic, not a guaranteed inverse: it splits ACQBY on
the first "/" and calls the left half system. That works
exactly when a file’s ACQBY was written as "System / Serial"
in the first place. Neither real EDI used throughout this section was:
Note
gv100.edi below is public-domain USGS data; see
Provenance and Bibliography for the required citation.
>>> from pathlib import Path
>>> from pycsamt.seg.edi import EDIFile
>>> edi_willy = EDIFile(Path("data/AMT/WILLY_DATA/L18PLT/18-001A.edi"))
>>> head_willy = edi_willy.get_section("head")
>>> print(repr(head_willy.acqby), repr(head_willy.progvers))
None 'MTPROC V1.0.7'
>>> inst_willy = InstrumentMeta.from_head(head_willy)
>>> print(inst_willy)
<InstrumentMeta system='' serial=None>
>>> edi_gv = EDIFile(Path("data/gv_data/gv_final_edi/gv100.edi"))
>>> head_gv = edi_gv.get_section("head")
>>> print(repr(head_gv.acqby), repr(head_gv.progvers))
'Jared Peacock' '1.1.5'
>>> inst_gv = InstrumentMeta.from_head(head_gv)
>>> print(inst_gv)
<InstrumentMeta system='Jared Peacock' serial=None>
WILLY’s ACQBY was empty, so system comes back empty too –
harmless. Gabbs Valley’s ACQBY holds the person who acquired the
data, not an instrument string, and from_head has no way to tell
the difference: it confidently reports the acquiring scientist’s name
as the system. Neither result is a parsing failure; from_head
faithfully reflects what the heuristic can extract from ACQBY
alone, and that heuristic simply does not hold for every real file.
Treat its output as a suggestion to verify, not as ground truth, and
prefer known_system() or a manual InstrumentMeta when
the real hardware is documented elsewhere (a field log, the survey’s
own metadata release, or – as with the presets above – domain
knowledge of what that project typically ran).
14.4.4. Serialization#
InstrumentMeta round-trips through JSON (and YAML, when
PyYAML is installed) the same way
SurveyMeta does in
Site and Survey Metadata, nesting each SensorSpec as a plain
dict:
>>> manual = InstrumentMeta(
... system="Phoenix V8",
... serial="V8-20473",
... magnetic_sensor=SensorSpec(
... sensor_type="induction_coil",
... model="MTC-150H",
... frequency_range=(1e-4, 2e3),
... ),
... electric_sensor=SensorSpec(
... sensor_type="electrode",
... model="Pb-PbCl2 porous pot",
... ),
... )
>>> text = manual.to_json()
>>> reloaded = InstrumentMeta.from_json(text)
>>> print(reloaded.magnetic_sensor)
<SensorSpec 'MTC-150H' [0.0001–2e+03 Hz]>
>>> print(reloaded == manual)
True
save/load wrap the same JSON/YAML methods and pick the format
from the file extension, so a preset built in memory can be written
once (inst.save("phoenix_v8.json")) and reloaded later without
re-specifying every sensor field.
14.4.5. Next Steps#
Site and Survey Metadata covers station identity and campaign-level aggregation;
Channels and Orientation covers the physical channel geometry these sensors are wired into;
Processing and Quality covers how the resulting transfer function was estimated and how good the result is.