Source code for SDF.data_model.array_data_2d

import re
from ast import literal_eval
from itertools import islice
from xml.etree.ElementTree import Element

import numpy as np

from SDF.data_model._helper_functions import pop_element_text, pop_element_attribute, element_is_empty
from SDF.data_model.abstract import Data


[docs]class ArrayData2D(Data): """Wraps a 2-dimensional Numpy ndarray, represents SDF <data> elements of dataset type 'mc'""" def __init__(self, data: np.ndarray): if not isinstance(data, np.ndarray): raise TypeError(f"Expected a numpy ndarray, got {type(data)}") if data.ndim != 2: raise ValueError(f"Expected a 2D array, got {data.ndim} dimensions (shape: {data.shape})") self.__array = data @property def data(self): """The wrapped array""" return self.__array @property def type_for_xml(self) -> str: return "mc"
[docs] def to_xml_element(self) -> Element: element = Element("data") # shape rows, cols = self.data.shape element.attrib["rows"] = str(rows) element.attrib["cols"] = str(cols) # data to 1D string iterable if np.issubdtype(self.data.dtype, np.integer): element.attrib["type"] = "int" text_iter = map(str, self.data.flatten()) elif np.issubdtype(self.data.dtype, np.floating): element.attrib["type"] = "float" text_iter = map(str, self.data.flatten()) else: raise ValueError(f"Cannot handle data of dtype {self.data.dtype}") # format data to XML text element.text = "\n".join(" ".join(islice(text_iter, 0, cols)) for _ in range(rows)) return element
[docs] @classmethod def from_xml_element(cls, element: Element) -> "ArrayData2D": if element.tag != "data": raise ValueError(f"Expected a <data> element, got {element.tag}") text = pop_element_text(element) xml_dtype = pop_element_attribute(element, "type") # read shape if "rows" in element.attrib and "cols" in element.attrib: # remove "shape" attribute of old multi-column SDF class if "shape" in element.attrib: pop_element_attribute(element, "shape") # <data rows="..." cols="..." ... > rows = int(pop_element_attribute(element, "rows")) cols = int(pop_element_attribute(element, "cols")) if rows == 1: shape = (cols,) elif cols == 1: shape = (rows,) else: shape = (rows, cols) elif "shape" in element.attrib: # <data shape="(rows, cols)" ... >, as generated by old multi-column SDF class shape_str = pop_element_attribute(element, "shape") if re.match(r"\([0-9]+, ?[0-9]+\)", shape_str): # like '(50, 10)' rows, cols = literal_eval(shape_str) shape = (rows, cols) else: raise ValueError(f"Expected a 2D shape string like '(50, 10)', got '{shape_str}'") else: raise ValueError("No shape information found") # read data if xml_dtype == "int": data = np.fromiter(map(int, text.split()), dtype=int) elif xml_dtype == "float": data = np.fromiter(map(float, text.split()), dtype=float) else: raise ValueError(f"Expected attribute 'type' to be 'int', 'float' or 'hex', got '{xml_dtype}'") if not element_is_empty(element): raise ValueError("Element is not empty") return cls(data.reshape(shape))
def __repr__(self): return f"{self.__class__.__name__}({self.data!r})" def __eq__(self, other): if isinstance(other, ArrayData2D): return (self.data.shape == other.data.shape and np.allclose(self.data, other.data, equal_nan=True)) return False