2.7.5.1. pycsamt.airborne.validation#

Shared parameter validation and normalization for pycsamt.airborne.

This module centralizes the boundary-validation logic that would otherwise be reimplemented independently by pycsamt.airborne.base, pycsamt.airborne.navigation, and each technology adapter (pycsamt.airborne.mobilemt, pycsamt.airborne.ztem, pycsamt.airborne.afmag). Every helper accepts an error_cls keyword so a technology adapter can keep raising its own public exception type (for example MobileMTValidationError) while sharing one implementation, instead of every adapter re-declaring an equivalent private helper under a different name.

Structural, type, and shape validation is the majority of this module. Scientific invariants that are specific to one technology (for example the MobileMT 3x2 admittance matrix shape) remain the responsibility of the owning adapter module, consistent with the pyCSAMT convention that public methods validate near the API boundary while private helpers may assume already-validated internal state. A small number of helpers (reference_station_mapping(), merge_remote_reference_processing()) go slightly beyond pure validation into shared EMTF-metadata assembly, because that assembly logic was independently duplicated across technology adapters just as much as the shape checks were – keeping it out of this module would not have made the package simpler, only moved the duplication somewhere less discoverable.

Functions

clean_identifier(value, *, name[, error_cls])

Return a stripped, non-empty string identifier.

emtf_class()

Return pycsamt.emtf.EMTF, imported lazily.

merge_remote_reference_processing(...[, ...])

Merge a duck-typed reference station into remote-reference metadata.

normalize_count_range(value, *, name[, ...])

Return a validated (low, high) count with 0 < low <= high.

normalize_estimate_array(value, *, ...[, ...])

Return a validated (n_frequency, *tail) estimate array.

normalize_fixed_channels(value, *, expected, ...)

Return value as a stripped tuple, requiring it to equal expected.

normalize_frequency(value, *[, name, error_cls])

Return a validated 1-D, finite, strictly positive vector.

normalize_frequency_range(value, *, name[, ...])

Return a validated (low, high) band with 0 < low < high.

normalize_numeric_vector(value, *, name, size)

Return a validated 1-D float vector aligned to size, or None.

normalize_object_vector(value, *, name, size)

Return a validated length-size tuple of opaque values, or None.

normalize_optional_identifier(value)

Return a stripped identifier, or None if absent/blank.

normalize_positive_float(value, *, name[, ...])

Return value as a finite, strictly positive float.

normalize_record_mask(value, *, n_samples[, ...])

Return a boolean record-coverage mask of length n_samples.

normalize_sample_axis_array(value, *, name, ...)

Return a validated array with a leading sample axis of n_samples.

reference_station_mapping(reference_station, ...)

Return a duck-typed reference station as a plain EMTF.attrs map.

resolve_frequency_or_periods(*, frequency, ...)

Return validated (frequency, periods) from exactly one input.

resolve_line_frequency_grid(frequency, *, ...)

Return (common_frequency, frequency_rows) for one flight line.

pycsamt.airborne.validation.emtf_class()[source]

Return pycsamt.emtf.EMTF, imported lazily.

Both pycsamt.airborne.base and pycsamt.airborne.qc need EMTF only for isinstance checks. Importing it lazily here, once, avoids a hard import-time dependency on pycsamt.emtf from either module while keeping the check itself in one place instead of duplicated per module.

pycsamt.airborne.validation.clean_identifier(value, *, name, error_cls=<class 'ValueError'>)[source]

Return a stripped, non-empty string identifier.

Parameters:
  • value (Any) – Candidate identifier. str(value) is used before stripping.

  • name (str) – Parameter name used in the error message.

  • error_cls (type, default ValueError) – Exception type raised when value strips to an empty string.

Returns:

The stripped identifier.

Return type:

str

Raises:

error_cls – If value is empty after stripping.

pycsamt.airborne.validation.normalize_optional_identifier(value)[source]

Return a stripped identifier, or None if absent/blank.

Unlike clean_identifier(), an empty result is not an error: this is for genuinely optional identifiers (for example a reference station’s station_id before it falls back to a SiteMeta name), where “not supplied” and “supplied but blank” should collapse to the same None rather than one being valid and the other raising.

Parameters:

value (Any | None)

Return type:

str | None

pycsamt.airborne.validation.normalize_positive_float(value, *, name, error_cls=<class 'ValueError'>)[source]

Return value as a finite, strictly positive float.

Shared by every technology SystemSpec publishing a single positive descriptive rate/measurement (a sampling rate, an output rate, a coil angle, …) as opposed to a range; see normalize_frequency_range() for the two-value case.

Parameters:
Return type:

float

pycsamt.airborne.validation.normalize_count_range(value, *, name, error_cls=<class 'ValueError'>)[source]

Return a validated (low, high) count with 0 < low <= high.

Shared by every technology SystemSpec publishing a typical minimum/maximum item count (for example a typical processed frequency-window count). Unlike normalize_frequency_range(), equality (low == high) is valid here: a system with a fixed count still has “typical min == typical max”.

Parameters:
Return type:

tuple[int, int]

pycsamt.airborne.validation.normalize_numeric_vector(value, *, name, size, error_cls=<class 'ValueError'>)[source]

Return a validated 1-D float vector aligned to size, or None.

Parameters:
  • value (array-like or None) – Candidate numeric vector. None passes through unchanged rather than being treated as a physical zero.

  • name (str) – Parameter name used in error messages.

  • size (int) – Required vector length.

  • error_cls (type, default ValueError) – Exception type raised on shape/finiteness violations.

Returns:

NaN values are permitted: an individual missing sample is represented by nan, never by a fabricated zero.

Return type:

ndarray of shape (size,), or None

Raises:

error_cls – If value is not 1-D, its length does not match size, or it contains an infinite value.

pycsamt.airborne.validation.normalize_object_vector(value, *, name, size, error_cls=<class 'ValueError'>)[source]

Return a validated length-size tuple of opaque values, or None.

Used for sample-aligned fields, such as timestamps, whose element type is not itself numeric or geophysical.

Parameters:
Return type:

tuple[Any, …] | None

pycsamt.airborne.validation.normalize_frequency(value, *, name='frequency', error_cls=<class 'ValueError'>)[source]

Return a validated 1-D, finite, strictly positive vector.

Parameters:
  • value (array-like) – Candidate frequency (Hz) or period (s) values. A scalar is promoted to a length-1 array.

  • name (str, default "frequency") – Parameter name used in error messages; pass "periods" when validating a period axis instead of a frequency axis.

  • error_cls (type, default ValueError) – Exception type raised on shape/positivity violations.

Returns:

Finite, strictly positive values.

Return type:

ndarray of shape (n,)

Raises:

error_cls – If value is not 1-D, is empty, or contains a non-finite or non-positive value.

pycsamt.airborne.validation.resolve_frequency_or_periods(*, frequency, periods, error_cls=<class 'ValueError'>)[source]

Return validated (frequency, periods) from exactly one input.

Exactly one of frequency or periods must be supplied; the other axis is derived as its reciprocal. This is the shared contract used by every airborne technology adapter that accepts either axis.

Returns:

(frequency, periods), each of shape (n,).

Return type:

(ndarray, ndarray)

Raises:

error_cls – If both or neither of frequency/periods are supplied, or if the supplied axis fails normalize_frequency().

Parameters:
pycsamt.airborne.validation.resolve_line_frequency_grid(frequency, *, n_samples, n_frequency, error_cls=<class 'ValueError'>)[source]

Return (common_frequency, frequency_rows) for one flight line.

Parameters:
  • frequency (array-like) – Either one shared frequency vector of shape (n_frequency,) or a per-sample grid of shape (n_samples, n_frequency).

  • n_samples (int) – Number of navigation samples on the line.

  • n_frequency (int) – Number of frequency samples expected from the response data.

  • error_cls (type, default ValueError) – Exception type raised on shape violations.

Returns:

Exactly one of (common_frequency, None) or (None, frequency_rows).

Return type:

(ndarray or None, ndarray or None)

Raises:

error_cls – If frequency is 1-D but its length does not match n_frequency, or if it is neither a valid (n_frequency,) vector nor a (n_samples, n_frequency) matrix.

Notes

The shared 1-D case is fully validated here via normalize_frequency(). The per-sample 2-D case is only shape-checked: each row’s own finiteness/positivity is validated lazily, once per attached sample, by the caller (typically via another normalize_frequency() call inside its per-sample loop). This means a row for a sample excluded by a line’s record_mask is never required to be valid – consistent with the rest of this module’s missing-is-not-invalid stance, since a masked-out sample contributes no record for that row to belong to.

pycsamt.airborne.validation.normalize_frequency_range(value, *, name, error_cls=<class 'ValueError'>)[source]

Return a validated (low, high) band with 0 < low < high.

Shared by every technology SystemSpec that publishes a nominal or practical frequency band as descriptive metadata.

Parameters:
Return type:

tuple[float, float]

pycsamt.airborne.validation.normalize_fixed_channels(value, *, expected, name, error_cls=<class 'ValueError'>)[source]

Return value as a stripped tuple, requiring it to equal expected.

Several technology contracts (for example MobileMT’s Ex/Ey admittance inputs) fix the channel layout by scientific definition rather than by user choice. Centralizing this “must equal” check keeps the behavior and message consistent across adapters.

Parameters:
Return type:

tuple[str, …]

pycsamt.airborne.validation.normalize_estimate_array(value, *, n_frequency, tail, name, error_cls=<class 'ValueError'>)[source]

Return a validated (n_frequency, *tail) estimate array.

A single tail-shaped matrix is promoted to one frequency. This is the shared contract for VAR/INVSIGCOV/RESIDCOV payloads across technology adapters.

Parameters:
Return type:

ndarray

pycsamt.airborne.validation.normalize_sample_axis_array(value, *, name, n_samples, expected, error_cls=<class 'ValueError'>)[source]

Return a validated array with a leading sample axis of n_samples.

When n_samples == 1 a caller may omit the leading axis; it is promoted automatically. This is the shared contract used to attach per-sample transfer-function arrays (admittance, tipper, tensor, …) and per-sample statistical estimates to a flight line.

Parameters:
Return type:

ndarray

pycsamt.airborne.validation.normalize_record_mask(value, *, n_samples, error_cls=<class 'ValueError'>)[source]

Return a boolean record-coverage mask of length n_samples.

None means every navigation sample has an attached EM record.

Parameters:
Return type:

ndarray

pycsamt.airborne.validation.reference_station_mapping(reference_station, *, channel_fields)[source]

Return a duck-typed reference station as a plain EMTF.attrs map.

Every technology’s *ReferenceStation metadata class (MobileMT’s, ZTEM’s, AFMAG’s) exposes preferred_id, one or more channel-name tuples, an optional site, and an optional attrs dict; this is the shared shape those adapters build into EMTF.attrs["<technology>"]["reference_station"].

Parameters:
  • reference_station (Any or None) – Duck-typed reference-station object exposing preferred_id, site, attrs, and one attribute per name in channel_fields. None returns None.

  • channel_fields (tuple of str) – Attribute names to read off reference_station and include verbatim under the same key, for example ("electric_channels",) for MobileMT or ("measured_channels", "transfer_input_channels") for AirMt.

Returns:

None when reference_station is None; otherwise a mapping with "station_id", one entry per channel_fields name, and "site"/"attrs" when those are non-empty.

Return type:

dict or None

pycsamt.airborne.validation.merge_remote_reference_processing(reference_station, processing, *, reference_type, technology, extra=mappingproxy({}), error_cls=<class 'ValueError'>)[source]

Merge a duck-typed reference station into remote-reference metadata.

reference_station is each technology’s scientifically typed way to supply its fixed ground reference; a caller-supplied processing is the general ProcessingMeta way. When both are given, they must describe the same reference site – this raises rather than silently letting one win. When only reference_station is given, a ProcessingMeta is synthesized around it so processing is always the one place downstream code (for example assess_airborne_qc()) looks for reference metadata, regardless of which technology built the EMTF.

Parameters:
  • reference_station (Any or None) – Duck-typed reference-station object exposing preferred_id. None returns processing unchanged.

  • processing (ProcessingMeta or None) – Caller-supplied processing metadata to merge into.

  • reference_type (str) – RemoteReferenceMeta.reference_type to record, for example "fixed_ground_magnetic".

  • technology (str) – Recorded as extra["technology"] on the synthesized RemoteReferenceMeta.

  • extra (Mapping, optional) – Additional technology-specific RemoteReferenceMeta.extra entries (for example measured/transfer channel names).

  • error_cls (type, default ValueError) – Exception type raised on a genuine site conflict. The type check on processing itself always raises TypeError, regardless of error_cls, since that failure is never a reference-station/processing conflict.

Returns:

processing, either unchanged, merged with the synthesized remote reference, or newly created around it.

Return type:

ProcessingMeta or None

Raises:
  • TypeError – If processing is supplied and is not a ProcessingMeta.

  • error_cls – If processing already has a remote reference whose site conflicts with reference_station’s.