Source code for xtgeo.metadata.metadata
"""The metadata module, currently experimental.
The metadata works through the various datatypes in XTGeo. For example::
>>> import xtgeo
>>> surf = xtgeo.surface_from_file(surface_dir + "/topreek_rota.gri")
>>> surf.metadata.required
dict([('ncol', 554),...
>>> surf.metadata.optional.mean = surf.values.mean()
"""
import xtgeo
from xtgeo.common.constants import UNDEF
from xtgeo.common.log import null_logger
logger = null_logger(__name__)
class _OptionalMetaData:
"""Optional metadata are not required, but keys are limited.
A limited sets of possible keys are available, and they can modified. This
class can also have validation methods.
"""
__slots__ = (
"_name",
"_shortname",
"_datatype",
"_md5sum",
"_description",
"_crs",
"_datetime",
"_deltadatetime",
"_visuals",
"_domain",
"_user",
"_field",
"_source",
"_modelid",
"_ensembleid",
"_units",
"_mean",
"_stddev",
"_percentiles",
)
def __init__(self) -> None:
self._name = "A Longer Descriptive Name e.g. from SMDA"
self._shortname = "TheShortName"
self._datatype = None
self._md5sum = None
self._description = "Some description"
self._crs = None
self._datetime = None
self._deltadatetime = None
self._visuals = {"colortable": "rainbow", "lower": None, "upper": None}
self._domain = "depth"
self._units = "metric"
self._mean = None
self._stddev = None
self._percentiles = None
self._user = "anonymous"
self._field = "nofield"
self._ensembleid = None
self._modelid = None
self._source = "unknown"
@property
def name(self):
return self._name
@name.setter
def name(self, newname):
# TODO: validation
self._name = newname
@property
def datetime(self):
return self._datetime
@datetime.setter
def datetime(self, newdate):
# TODO: validation
self._datetime = newdate
@property
def shortname(self):
return self._shortname
@shortname.setter
def shortname(self, newname):
if not isinstance(newname, str):
raise ValueError("The shortname must be a string.")
if len(newname) >= 32:
raise ValueError("The shortname length must less or equal 32 letters.")
self._shortname = newname
@property
def description(self):
return self._description
@description.setter
def description(self, newstr):
if not isinstance(newstr, str):
raise ValueError("The description must be a string.")
if len(newstr) >= 64:
raise ValueError("The description length must less or equal 64 letters.")
invalids = r"/$<>[]:\&%"
if set(invalids).intersection(newstr):
raise ValueError("The description contains invalid characters such as /.")
self._description = newstr
@property
def md5sum(self):
"""Set or get the md5 checksum of file content.
See generate_hash() method in e.g. RegularSurface.
"""
return self._md5sum
@md5sum.setter
def md5sum(self, newhash):
# TODO: validation
self._md5sum = newhash
def get_meta(self):
"""Return metadata as an dict."""
meta = {}
for key in self.__slots__:
newkey = key[1:]
meta[newkey] = getattr(self, key)
return meta
class MetaData:
"""Generic metadata class, not intended to be used directly."""
def __init__(self):
"""Generic metadata class __init__, not be used directly."""
self._required = {}
self._optional = _OptionalMetaData()
self._freeform = {}
self._freeform = {"smda": "whatever"}
def get_metadata(self):
"""Get all metadata that are present."""
allmeta = {}
allmeta["_required_"] = self._required
allmeta["_optional_"] = self._optional.get_meta()
allmeta["_freeform_"] = self._freeform
return allmeta
@property
def optional(self):
"""Return or set optional metadata.
When setting optional names, it can be done in several ways...
surf.metadata.optional.name = "New name"
"""
# return a copy of the instance; the reason for this is to avoid manipulation
# without validation
return self._optional.get_meta()
@optional.setter
def optional(self, indict):
# setting the optional key, including validation
if not isinstance(indict, dict):
raise ValueError(f"Input must be a dictionary, not a {type(indict)}")
for key, value in indict.items():
setattr(self._optional, "_" + key, value)
@property
def opt(self):
"""Return the metadata optional instance.
This makes access to the _OptionalMetaData instance.
Example::
>>> import xtgeo
>>> surf = xtgeo.surface_from_file(surface_dir + "/topreek_rota.gri")
>>> surf.metadata.opt.shortname = "TopValysar"
"""
return self._optional
@optional.setter
def optional(self, indict):
# setting the optional key, including validation
if not isinstance(indict, dict):
raise ValueError(f"Input must be a dictionary, not a {type(indict)}")
for key, value in indict.items():
setattr(self._optional, "_" + key, value)
@property
def freeform(self):
"""Get or set the current freeform metadata dictionary."""
return self._freeform
@freeform.setter
def freeform(self, adict):
"""Freeform is a whatever you want set, without any validation."""
self._freeform = adict.copy()
def generate_fmu_name(self):
"""Generate FMU name on form xxxx--yyyy--date but no suffix."""
fname = ""
first = "prefix"
fname += first
fname += "--"
fname += self._optional._shortname.lower()
if self._optional._datetime:
fname += "--"
fname += str(self._optional._datetime)
return fname
[docs]
class MetaDataRegularSurface(MetaData):
"""Metadata for RegularSurface() objects."""
REQUIRED = {
"ncol": 1,
"nrow": 1,
"xori": 0.0,
"yori": 0.0,
"xinc": 1.0,
"yinc": 1.0,
"yflip": 1,
"rotation": 0.0,
"undef": UNDEF,
}
[docs]
def __init__(self):
"""Docstring."""
super().__init__()
self._required = __class__.REQUIRED
self._optional._datatype = "Regular Surface"
@property
def required(self):
"""Get of set required metadata."""
return self._required
@required.setter
def required(self, obj):
if not isinstance(obj, xtgeo.RegularSurface):
raise ValueError("Input object is not a RegularSurface()")
self._required["ncol"] = obj.ncol
self._required["nrow"] = obj.nrow
self._required["xori"] = obj.xori
self._required["yori"] = obj.yori
self._required["xinc"] = obj.xinc
self._required["yinc"] = obj.yinc
self._required["yflip"] = obj.yflip
self._required["rotation"] = obj.rotation
self._required["undef"] = obj.undef
[docs]
class MetaDataRegularCube(MetaData):
"""Metadata for Cube() objects."""
# allowed optional keys; these are set to avoid discussions
REQUIRED = {
"ncol": 1,
"nrow": 1,
"nlay": 1,
"xori": 0.0,
"yori": 0.0,
"zori": 0.0,
"xinc": 1.0,
"yinc": 1.0,
"zinc": 1.0,
"yflip": 1,
"zflip": 1,
"rotation": 0.0,
"undef": UNDEF,
}
[docs]
def __init__(self):
"""Docstring."""
super().__init__()
self._required = __class__.REQUIRED
self._optional._datatype = "Regular Cube"
@property
def required(self):
"""Get of set required metadata."""
return self._required
@required.setter
def required(self, obj):
if not isinstance(obj, xtgeo.Cube):
raise ValueError("Input object is not a regular Cube()")
self._required["ncol"] = obj.ncol
self._required["nrow"] = obj.nrow
self._required["nlay"] = obj.nlay
self._required["xori"] = obj.xori
self._required["yori"] = obj.yori
self._required["zori"] = obj.zori
self._required["xinc"] = obj.xinc
self._required["yinc"] = obj.yinc
self._required["zinc"] = obj.zinc
self._required["yflip"] = obj.yflip
self._required["zflip"] = 1
self._required["rotation"] = obj.rotation
self._required["undef"] = obj.undef
[docs]
class MetaDataCPGeometry(MetaData):
"""Metadata for Grid() objects of type simplified CornerPoint Geometry."""
REQUIRED = {
"ncol": 1,
"nrow": 1,
"nlay": 1,
"xshift": 0.0,
"yshift": 0.0,
"zshift": 0.0,
"xscale": 1.0,
"yscale": 1.0,
"zscale": 1.0,
}
[docs]
def __init__(self):
"""Docstring."""
super().__init__()
self._required = __class__.REQUIRED
self._optional._datatype = "CornerPoint GridGeometry"
@property
def required(self):
"""Get of set required metadata."""
return self._required
@required.setter
def required(self, obj):
if not isinstance(obj, xtgeo.Grid):
raise ValueError("Input object is not a Grid()")
self._required["ncol"] = obj.ncol
self._required["nrow"] = obj.nrow
self._required["nlay"] = obj.nlay
self._required["xshift"] = 0.0 # hardcoded so far
self._required["yshift"] = 0.0
self._required["zshift"] = 0.0
self._required["xscale"] = 1.0
self._required["yscale"] = 1.0
self._required["zscale"] = 1.0
self._required["subgrids"] = obj.get_subgrids()
[docs]
class MetaDataCPProperty(MetaData):
"""Metadata for GridProperty() objects belonging to CPGeometry."""
REQUIRED = {
"ncol": 1,
"nrow": 1,
"nlay": 1,
"codes": None,
"discrete": False,
}
[docs]
def __init__(self):
"""Docstring."""
super().__init__()
self._required = __class__.REQUIRED
self._optional._datatype = "CornerPoint GridProperty"
@property
def required(self):
"""Get of set required metadata."""
return self._required
@required.setter
def required(self, obj):
if not isinstance(obj, xtgeo.GridProperty):
raise ValueError("Input object is not a GridProperty()")
self._required["ncol"] = obj.ncol
self._required["nrow"] = obj.nrow
self._required["nlay"] = obj.nlay
self._required["codes"] = obj.codes
self._required["discrete"] = obj.isdiscrete
[docs]
class MetaDataWell(MetaData):
"""Metadata for single Well() objects."""
REQUIRED = {
"rkb": 0.0,
"xpos": 0.0,
"ypos": 0.0,
"name": "noname",
"wlogs": {},
"mdlogname": None,
"zonelogname": None,
}
[docs]
def __init__(self):
"""Initialisation for Well metadata."""
super().__init__()
self._required = __class__.REQUIRED
self._optional._datatype = "Well"
@property
def required(self):
"""Get of set required metadata."""
return self._required
@required.setter
def required(self, obj):
if not isinstance(obj, xtgeo.Well):
raise ValueError("Input object is not a Well() instance!")
self._required["rkb"] = obj.rkb
self._required["xpos"] = obj.xpos
self._required["ypos"] = obj.ypos
self._required["name"] = obj.wname
self._required["wlogs"] = obj.get_wlogs()
self._required["mdlogname"] = obj.mdlogname
self._required["zonelogname"] = obj.zonelogname