Source code for pycsamt.metadata.processing
# Author: LKouadio <etanoyau@gmail.com>
# License: LGPL-3.0
"""Format-neutral electromagnetic processing metadata."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from ..api.property import PyCSAMTObject
from ._property import Software
__all__ = [
"RemoteReferenceMeta",
"ProcessingMeta",
"normalize_sign_convention",
]
[docs]
def normalize_sign_convention(value: str | None) -> str | None:
"""Normalize common Fourier sign-convention spellings.
Unknown non-empty values are preserved rather than silently coerced. This
is intentionally stricter than the historical SEG helper, which defaulted
unrecognized values to the positive convention.
"""
if value is None:
return None
raw = str(value).strip()
if not raw:
return None
text = raw.lower().replace("\\omega", "ω")
text = text.replace("omega", "ω").replace(" w ", " ω ")
compact = "".join(text.split())
if "exp(+i" in compact and ("ωt" in compact or "wt" in compact):
return "exp(+i ω t)"
if "exp(-i" in compact and ("ωt" in compact or "wt" in compact):
return "exp(-i ω t)"
return raw
[docs]
@dataclass(repr=False)
class RemoteReferenceMeta(PyCSAMTObject):
"""Describe remote-reference processing when it was used."""
reference_type: str | None = None
site: str | None = None
extra: dict[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
self.validate()
[docs]
def validate(self) -> None:
for attr in ("reference_type", "site"):
value = getattr(self, attr)
if value is not None:
text = str(value).strip()
setattr(self, attr, text or None)
self.extra = dict(self.extra or {})
[docs]
@dataclass(repr=False)
class ProcessingMeta(PyCSAMTObject):
"""Processing assumptions and software associated with an EMTF product.
This is the format-neutral canonical model. The historical
:class:`pycsamt.seg.property.Processing` object remains unchanged for EDI
compatibility and can be adapted later by the EDI interoperability layer.
"""
sign_convention: str | None = None
processed_by: str | None = None
software: Software | None = None
remote_reference: RemoteReferenceMeta | None = None
processing_tag: str | None = None
run_list: list[str] | None = None
extra: dict[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
self.validate()
[docs]
def validate(self) -> None:
self.sign_convention = normalize_sign_convention(self.sign_convention)
for attr in ("processed_by", "processing_tag"):
value = getattr(self, attr)
if value is not None:
text = str(value).strip()
setattr(self, attr, text or None)
if self.software is not None and not isinstance(
self.software, Software
):
raise TypeError("software must be a pycsamt.metadata.Software")
if self.remote_reference is not None and not isinstance(
self.remote_reference, RemoteReferenceMeta
):
raise TypeError("remote_reference must be RemoteReferenceMeta")
if self.run_list is not None:
if not isinstance(self.run_list, list):
raise TypeError("run_list must be a list of strings")
normalized: list[str] = []
for index, value in enumerate(self.run_list):
if not isinstance(value, str):
raise TypeError(f"run_list[{index}] must be str")
text = value.strip()
if text:
normalized.append(text)
self.run_list = normalized
self.extra = dict(self.extra or {})