From 8848d04bb9c8e94282694b33857727cda9cac1fb Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Mon, 2 Jan 2023 16:56:26 +0100 Subject: [PATCH 01/28] Create utils.py --- src/dcmReader/utils.py | 324 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 src/dcmReader/utils.py diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py new file mode 100644 index 0000000..1c2c30b --- /dev/null +++ b/src/dcmReader/utils.py @@ -0,0 +1,324 @@ +from __future__ import annotations + +import math +from typing import Protocol + +from dataclasses import dataclass, field + + +def _get_shape(ndarray: list | float) -> tuple[int, ...]: + """ + Get the shape of array-like. + + Examples + -------- + >>> _get_shape(1) + () + >>> _get_shape([]) + (0,) + >>> _get_shape([1]) + (1,) + >>> _get_shape([[1]]) + (1, 1) + >>> _get_shape([[1], [2]]) + (2, 1) + """ + # https://stackoverflow.com/questions/51960857/how-can-i-get-a-list-shape-without-using-numpy + if isinstance(ndarray, list): + # More dimensions, so make a recursive call + outermost_size = len(ndarray) + if outermost_size == 0: + return (outermost_size,) + else: + return (outermost_size, *_get_shape(ndarray[0])) + else: + # No more dimensions, so we're done + return () + + +class HasValuesProtocol(Protocol): + values: list[list[float]] + + +class ShapeRelatedMixin(HasValuesProtocol): + @property + def shape(self) -> tuple[int, ...]: + """ + Get shape of array. + + See Also + -------- + numpy.ndarray.shape + """ + return _get_shape(self.values) + + @property + def ndim(self) -> int: + """ + Get number of array dimensions. + + See Also + -------- + numpy.ndarray.ndim + """ + return len(self.shape) + + @property + def size(self) -> int: + """ + Get number of elements in the array. + + See Also + -------- + numpy.ndarray.size + """ + return math.prod(self.shape) + + def __len__(self) -> int: + try: + return self.shape[0] + except IndexError: + raise TypeError("len() of unsized object") + + +@dataclass +class _DcmBase(ShapeRelatedMixin): + name: str + values: list[list[float]] = field(default_factory=list) + coords: tuple[list[float], ...] = field(default_factory=tuple) + attrs: dict = field(default_factory=dict) + block_type: str = "" + + def __lt__(self, other): + return ( + self.attrs["function"] < other.attrs["function"] + and self.attrs["description"] < other.attrs["description"] + ) + + def _print_str(self, name: str, is_function: False) -> str: + """ + Print the data according to the dcm-format. + + Arrays longer than 6 are split to new line. + + Parameters + ---------- + name : str + Name of the block. + is_function : False + Is the block a 'FUNKTIONEN'. + """ + + def chunked(val, n=6): + yield from [val[i : i + n] for i in range(0, len(val), n)] + + def to_str(list_: str | int | float | list) -> str: + list__: list = [list_] if isinstance(list_, (str, int, float)) else list_ + return " ".join(map(str, list__)) + + if is_function: + return f'{name} {self.name} "{self.version}" "{self.description}"' + + value = f"{name} {self.name} {to_str(self.shape)}\n" + + ks = ( + ("LANGNAME", "description", lambda x: f'"{x}"'), + ("FUNKTION", "function", lambda x: f"{x}"), + ("DISPLAYNAME", "display_name", lambda x: f'"{x}"'), + ("EINHEIT_X", "units_x", lambda x: f'"{x}"'), + ("EINHEIT_Y", "units_y", lambda x: f'"{x}"'), + ("EINHEIT_W", "units", lambda x: f'"{x}"'), + ) + for k, v, f in ks: + if self.attrs.get(v, ""): + value += f" {k: <13} {f(self.attrs[v])}\n" + + ndim = self.ndim + for i, (coord, coord_label) in enumerate(zip(self.coords, ("X", "Y"))): + lbl = f"ST/{coord_label}" + value += f" {lbl: <13} {to_str(coord)}\n" + + if i == ndim - 1: + for value_entries in chunked(self.values[i]): + value += f" {'WERT': <13} {to_str(value_entries)}\n" + + # for i, x_entries in enumerate(chunked(self.coords[0])): + # value += f" {'ST/X': <13} {to_str(x_entries)}\n" + + # for i, y_entry in enumerate(self.coords[1]): + # value += f" {'ST/Y': <13} {y_entry}\n" + + # for value_entries in chunked(self.values[i]): + # value += f" {'WERT': <13} {to_str(value_entries)}\n" + + for var_name, var_value in self.attrs["variants"].items(): + value += f" {'VAR': <13} {var_name}={var_value}\n" + + value += "END" + + return value + + def __str__(self) -> str: + is_function = True if self.block_type == "FUNKTIONEN" else False + return self._print_str(self.block_type, is_function) + + +class _DcmBaseOld: + _name: str + _attrs: dict + _values: list[list[float]] + _coords: tuple[list[float], ...] + + __slots__ = ("_name", "_attrs", "_values", "_coords", "_block_type") + + def __init__( + self, + name: str, + values: list[list[float]] | None = None, + coords: tuple[list[float], ...] = None, + attrs: dict | None = None, + *, + block_type: str = "", + ): + self.name = name + self.values = [] if values is None else values + self.coords = () if coords is None else coords + self.attrs = {} if attrs is None else attrs + + self._block_type = block_type + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str) -> None: + self._name = value + + @property + def attrs(self) -> dict: + return self._attrs + + @attrs.setter + def attrs(self, value: dict) -> None: + self._attrs = value + + @property + def values(self) -> list[list[float]]: + """Return only values WERT, no x or y axis values.""" + return self._values + + @values.setter + def values(self, value: list[list[float]]) -> None: + self._values = value + + @property + def coords(self) -> tuple[list[float], ...]: + """Return x or y axis values.""" + return self._coords + + @coords.setter + def coords(self, value: tuple[list[float], ...]) -> None: + """Return x or y axis values.""" + self._coords = value + + @property + def shape(self) -> tuple[int, ...]: + """ + Get shape of array. + + See Also + -------- + numpy.ndarray.shape + """ + return _get_shape(self.values) + + @property + def ndim(self) -> int: + """ + Get number of array dimensions. + + See Also + -------- + numpy.ndarray.ndim + """ + return len(self.shape) + + @property + def size(self) -> int: + """ + Get number of elements in the array. + + See Also + -------- + numpy.ndarray.size + """ + return math.prod(self.shape) + + def __len__(self) -> int: + try: + return self.shape[0] + except IndexError: + raise TypeError("len() of unsized object") + + def __lt__(self, other): + return ( + self.attrs["function"] < other.attrs["function"] + and self.attrs["description"] < other.attrs["description"] + ) + + def _print_str(self, name: str, is_function: False) -> str: + """ + Print the data according to the dcm-format. + + Arrays longer than 6 are split to new line. + + Parameters + ---------- + name : str + Name of the block. + is_function : False + Is the block a 'FUNKTIONEN'. + """ + + def chunked(val, n=6): + yield from [val[i : i + n] for i in range(0, len(val), n)] + + def to_str(list_) -> str: + return " ".join(map(str, list_)) + + if is_function: + return f'{name} {self.name} "{self.version}" "{self.description}"' + + value = f"{name} {self.name} {to_str(self.shape)}\n" + + ks = ( + ("LANGNAME", "description", lambda x: f'"{x}"'), + ("FUNKTION", "function", lambda x: f"{x}"), + ("DISPLAYNAME", "display_name", lambda x: f'"{x}"'), + ("EINHEIT_X", "units_x", lambda x: f'"{x}"'), + ("EINHEIT_Y", "units_y", lambda x: f'"{x}"'), + ("EINHEIT_W", "units", lambda x: f'"{x}"'), + ) + for k, v, f in ks: + if self.attrs.get(v, ""): + value += f" {k: <13} {f(self.attrs[v])}\n" + + for x_entries in chunked(self.coords[0]): + value += f" {'ST/X': <13} {to_str(x_entries)}\n" + + for y_entry, values in zip(self.coords[1], self.values): + value += f" {'ST/Y': <13} {str(y_entry)}\n" + for value_entries in chunked(values): + value += f" {'WERT': <13} {to_str(value_entries)}\n" + + for var_name, var_value in self.attrs["variants"].items(): + value += f" {'VAR': <13} {var_name}={var_value}\n" + + value += "END" + + return value + + def __str__(self) -> str: + is_function = True if self._block_type == "FUNKTIONEN" else False + return self._print_str(self._block_type, is_function) From b02c177cde0b36de6a5d45035dacfcd67ed93d92 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Mon, 2 Jan 2023 16:57:08 +0100 Subject: [PATCH 02/28] Update utils.py --- src/dcmReader/utils.py | 161 ----------------------------------------- 1 file changed, 161 deletions(-) diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py index 1c2c30b..233fe58 100644 --- a/src/dcmReader/utils.py +++ b/src/dcmReader/utils.py @@ -161,164 +161,3 @@ def to_str(list_: str | int | float | list) -> str: def __str__(self) -> str: is_function = True if self.block_type == "FUNKTIONEN" else False return self._print_str(self.block_type, is_function) - - -class _DcmBaseOld: - _name: str - _attrs: dict - _values: list[list[float]] - _coords: tuple[list[float], ...] - - __slots__ = ("_name", "_attrs", "_values", "_coords", "_block_type") - - def __init__( - self, - name: str, - values: list[list[float]] | None = None, - coords: tuple[list[float], ...] = None, - attrs: dict | None = None, - *, - block_type: str = "", - ): - self.name = name - self.values = [] if values is None else values - self.coords = () if coords is None else coords - self.attrs = {} if attrs is None else attrs - - self._block_type = block_type - - @property - def name(self) -> str: - return self._name - - @name.setter - def name(self, value: str) -> None: - self._name = value - - @property - def attrs(self) -> dict: - return self._attrs - - @attrs.setter - def attrs(self, value: dict) -> None: - self._attrs = value - - @property - def values(self) -> list[list[float]]: - """Return only values WERT, no x or y axis values.""" - return self._values - - @values.setter - def values(self, value: list[list[float]]) -> None: - self._values = value - - @property - def coords(self) -> tuple[list[float], ...]: - """Return x or y axis values.""" - return self._coords - - @coords.setter - def coords(self, value: tuple[list[float], ...]) -> None: - """Return x or y axis values.""" - self._coords = value - - @property - def shape(self) -> tuple[int, ...]: - """ - Get shape of array. - - See Also - -------- - numpy.ndarray.shape - """ - return _get_shape(self.values) - - @property - def ndim(self) -> int: - """ - Get number of array dimensions. - - See Also - -------- - numpy.ndarray.ndim - """ - return len(self.shape) - - @property - def size(self) -> int: - """ - Get number of elements in the array. - - See Also - -------- - numpy.ndarray.size - """ - return math.prod(self.shape) - - def __len__(self) -> int: - try: - return self.shape[0] - except IndexError: - raise TypeError("len() of unsized object") - - def __lt__(self, other): - return ( - self.attrs["function"] < other.attrs["function"] - and self.attrs["description"] < other.attrs["description"] - ) - - def _print_str(self, name: str, is_function: False) -> str: - """ - Print the data according to the dcm-format. - - Arrays longer than 6 are split to new line. - - Parameters - ---------- - name : str - Name of the block. - is_function : False - Is the block a 'FUNKTIONEN'. - """ - - def chunked(val, n=6): - yield from [val[i : i + n] for i in range(0, len(val), n)] - - def to_str(list_) -> str: - return " ".join(map(str, list_)) - - if is_function: - return f'{name} {self.name} "{self.version}" "{self.description}"' - - value = f"{name} {self.name} {to_str(self.shape)}\n" - - ks = ( - ("LANGNAME", "description", lambda x: f'"{x}"'), - ("FUNKTION", "function", lambda x: f"{x}"), - ("DISPLAYNAME", "display_name", lambda x: f'"{x}"'), - ("EINHEIT_X", "units_x", lambda x: f'"{x}"'), - ("EINHEIT_Y", "units_y", lambda x: f'"{x}"'), - ("EINHEIT_W", "units", lambda x: f'"{x}"'), - ) - for k, v, f in ks: - if self.attrs.get(v, ""): - value += f" {k: <13} {f(self.attrs[v])}\n" - - for x_entries in chunked(self.coords[0]): - value += f" {'ST/X': <13} {to_str(x_entries)}\n" - - for y_entry, values in zip(self.coords[1], self.values): - value += f" {'ST/Y': <13} {str(y_entry)}\n" - for value_entries in chunked(values): - value += f" {'WERT': <13} {to_str(value_entries)}\n" - - for var_name, var_value in self.attrs["variants"].items(): - value += f" {'VAR': <13} {var_name}={var_value}\n" - - value += "END" - - return value - - def __str__(self) -> str: - is_function = True if self._block_type == "FUNKTIONEN" else False - return self._print_str(self._block_type, is_function) From 6204d2bf3a62b3b804aac12d4eaf407b98532e49 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Mon, 2 Jan 2023 16:58:09 +0100 Subject: [PATCH 03/28] Update utils.py --- src/dcmReader/utils.py | 190 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 177 insertions(+), 13 deletions(-) diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py index 233fe58..c75ebe6 100644 --- a/src/dcmReader/utils.py +++ b/src/dcmReader/utils.py @@ -1,3 +1,6 @@ +""" +Definition of DCM characteristic map +""" from __future__ import annotations import math @@ -133,23 +136,23 @@ def to_str(list_: str | int | float | list) -> str: if self.attrs.get(v, ""): value += f" {k: <13} {f(self.attrs[v])}\n" - ndim = self.ndim - for i, (coord, coord_label) in enumerate(zip(self.coords, ("X", "Y"))): - lbl = f"ST/{coord_label}" - value += f" {lbl: <13} {to_str(coord)}\n" + # ndim = self.ndim + # for i, (coord, coord_label) in enumerate(zip(self.coords, ("X", "Y"))): + # lbl = f"ST/{coord_label}" + # value += f" {lbl: <13} {to_str(coord)}\n" - if i == ndim - 1: - for value_entries in chunked(self.values[i]): - value += f" {'WERT': <13} {to_str(value_entries)}\n" + # if i == ndim - 1: + # for value_entries in chunked(self.values[i]): + # value += f" {'WERT': <13} {to_str(value_entries)}\n" - # for i, x_entries in enumerate(chunked(self.coords[0])): - # value += f" {'ST/X': <13} {to_str(x_entries)}\n" + for i, x_entries in enumerate(chunked(self.coords[0])): + value += f" {'ST/X': <13} {to_str(x_entries)}\n" - # for i, y_entry in enumerate(self.coords[1]): - # value += f" {'ST/Y': <13} {y_entry}\n" + for i, y_entry in enumerate(self.coords[1]): + value += f" {'ST/Y': <13} {y_entry}\n" - # for value_entries in chunked(self.values[i]): - # value += f" {'WERT': <13} {to_str(value_entries)}\n" + for value_entries in chunked(self.values[i]): + value += f" {'WERT': <13} {to_str(value_entries)}\n" for var_name, var_value in self.attrs["variants"].items(): value += f" {'VAR': <13} {var_name}={var_value}\n" @@ -161,3 +164,164 @@ def to_str(list_: str | int | float | list) -> str: def __str__(self) -> str: is_function = True if self.block_type == "FUNKTIONEN" else False return self._print_str(self.block_type, is_function) + + +class _DcmBaseOld: + _name: str + _attrs: dict + _values: list[list[float]] + _coords: tuple[list[float], ...] + + __slots__ = ("_name", "_attrs", "_values", "_coords", "_block_type") + + def __init__( + self, + name: str, + values: list[list[float]] | None = None, + coords: tuple[list[float], ...] = None, + attrs: dict | None = None, + *, + block_type: str = "", + ): + self.name = name + self.values = [] if values is None else values + self.coords = () if coords is None else coords + self.attrs = {} if attrs is None else attrs + + self._block_type = block_type + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str) -> None: + self._name = value + + @property + def attrs(self) -> dict: + return self._attrs + + @attrs.setter + def attrs(self, value: dict) -> None: + self._attrs = value + + @property + def values(self) -> list[list[float]]: + """Return only values WERT, no x or y axis values.""" + return self._values + + @values.setter + def values(self, value: list[list[float]]) -> None: + self._values = value + + @property + def coords(self) -> tuple[list[float], ...]: + """Return x or y axis values.""" + return self._coords + + @coords.setter + def coords(self, value: tuple[list[float], ...]) -> None: + """Return x or y axis values.""" + self._coords = value + + @property + def shape(self) -> tuple[int, ...]: + """ + Get shape of array. + + See Also + -------- + numpy.ndarray.shape + """ + return _get_shape(self.values) + + @property + def ndim(self) -> int: + """ + Get number of array dimensions. + + See Also + -------- + numpy.ndarray.ndim + """ + return len(self.shape) + + @property + def size(self) -> int: + """ + Get number of elements in the array. + + See Also + -------- + numpy.ndarray.size + """ + return math.prod(self.shape) + + def __len__(self) -> int: + try: + return self.shape[0] + except IndexError: + raise TypeError("len() of unsized object") + + def __lt__(self, other): + return ( + self.attrs["function"] < other.attrs["function"] + and self.attrs["description"] < other.attrs["description"] + ) + + def _print_str(self, name: str, is_function: False) -> str: + """ + Print the data according to the dcm-format. + + Arrays longer than 6 are split to new line. + + Parameters + ---------- + name : str + Name of the block. + is_function : False + Is the block a 'FUNKTIONEN'. + """ + + def chunked(val, n=6): + yield from [val[i : i + n] for i in range(0, len(val), n)] + + def to_str(list_) -> str: + return " ".join(map(str, list_)) + + if is_function: + return f'{name} {self.name} "{self.version}" "{self.description}"' + + value = f"{name} {self.name} {to_str(self.shape)}\n" + + ks = ( + ("LANGNAME", "description", lambda x: f'"{x}"'), + ("FUNKTION", "function", lambda x: f"{x}"), + ("DISPLAYNAME", "display_name", lambda x: f'"{x}"'), + ("EINHEIT_X", "units_x", lambda x: f'"{x}"'), + ("EINHEIT_Y", "units_y", lambda x: f'"{x}"'), + ("EINHEIT_W", "units", lambda x: f'"{x}"'), + ) + for k, v, f in ks: + if self.attrs.get(v, ""): + value += f" {k: <13} {f(self.attrs[v])}\n" + + for x_entries in chunked(self.coords[0]): + value += f" {'ST/X': <13} {to_str(x_entries)}\n" + + for y_entry, values in zip(self.coords[1], self.values): + value += f" {'ST/Y': <13} {str(y_entry)}\n" + for value_entries in chunked(values): + value += f" {'WERT': <13} {to_str(value_entries)}\n" + + for var_name, var_value in self.attrs["variants"].items(): + value += f" {'VAR': <13} {var_name}={var_value}\n" + + value += "END" + + return value + + def __str__(self) -> str: + is_function = True if self._block_type == "FUNKTIONEN" else False + return self._print_str(self._block_type, is_function) From 74afe4bf4067ea5758c416d4de4556914ec64bd3 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Mon, 2 Jan 2023 17:10:26 +0100 Subject: [PATCH 04/28] Update dcm_reader.py --- src/dcmReader/dcm_reader.py | 608 +++++++++++++++++++++++++----------- 1 file changed, 420 insertions(+), 188 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index c9eec47..5d22a41 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -116,17 +116,17 @@ def read(self, file) -> None: """ _dcmFormat = None - comment_qualifier = ("!", "*", ".") - with open(file, "r") as f: for line in f: # Remove whitespaces line = line.strip() # Check if line is comment - if line.startswith(comment_qualifier): + if line.startswith(("!", "*", ".")): if not self._fileHeaderFinished: - self._fileHeader = self._fileHeader + line[1:].strip() + os.linesep + self._fileHeader = ( + self._fileHeader + line[1:].strip() + os.linesep + ) continue # At this point first comment block passed @@ -139,11 +139,15 @@ def read(self, file) -> None: # Check if format version line if _dcmFormat is None: if line.startswith("KONSERVIERUNG_FORMAT"): - _dcmFormat = float(re.search(r"(\d\.\d)", line.strip()).group(1)) + _dcmFormat = float( + re.search(r"(\d\.\d)", line.strip()).group(1) + ) continue else: logging.info(f"Found line: {line}") - raise Exception("Incorrect file structure. DCM file format has to be first entry!") + raise Exception( + "Incorrect file structure. DCM file format has to be first entry!" + ) # Check if functions start if line.startswith("FUNKTIONEN"): @@ -151,7 +155,9 @@ def read(self, file) -> None: line = f.readline() if line.startswith("END"): break - functionMatch = re.search(r"FKT (.*?)(?: \"(.*?)?\"(?: \"(.*?)?\")?)?$", line.strip()) + functionMatch = re.search( + r"FKT (.*?)(?: \"(.*?)?\"(?: \"(.*?)?\")?)?$", line.strip() + ) self._functionsList.append( DcmFunction( functionMatch.group(1), @@ -175,36 +181,46 @@ def read(self, file) -> None: elif line.startswith("FUNKTION"): foundParameter.function = self.parseString(line) elif line.startswith("WERT"): - foundParameter.value = self.convertValue(line.split(" ", 1)[1].strip()) + foundParameter.value = self.convertValue( + line.split(" ", 1)[1].strip() + ) elif line.startswith("EINHEIT_W"): foundParameter.unit = self.parseString(line) elif line.startswith("VAR"): foundParameter.variants.update(self.parseVariant(line)) elif line.startswith("TEXT"): foundParameter.text = self.parseString(line) - elif line.startswith(comment_qualifier): - if foundParameter.comment is None: - foundParameter.comment = line[1:].strip() + os.linesep - else: - foundParameter.comment += line[1:].strip() + os.linesep else: - logger.warning(f"Unknown parameter field: {line}") + if not line.startswith("*"): + logger.warning(f"Unknown parameter field: {line}") self._parameterList.append(foundParameter) # Check if parameter block start elif line.startswith("FESTWERTEBLOCK"): - blockData = re.search(r"FESTWERTEBLOCK\s+(.*?)\s+(\d+)(?:\s+\@\s+(\d+))?", line.strip()) + blockData = re.search( + r"FESTWERTEBLOCK\s+(.*?)\s+(\d+)(?:\s+\@\s+(\d+))?", + line.strip(), + ) foundBlockParameter = DcmParameterBlock(blockData.group(1)) - foundBlockParameter.x_dimension = self.convertValue(blockData.group(2)) + foundBlockParameter.x_dimension = self.convertValue( + blockData.group(2) + ) foundBlockParameter.y_dimension = ( - self.convertValue(blockData.group(3)) if blockData.group(3) is not None else 1 + self.convertValue(blockData.group(3)) + if blockData.group(3) is not None + else 1 ) while True: line = f.readline().strip() if line.startswith("END"): - if len(foundBlockParameter.values) != foundBlockParameter.y_dimension: - logger.error(f"Y dimension in {foundBlockParameter.name} do not match description!") + if ( + len(foundBlockParameter.values) + != foundBlockParameter.y_dimension + ): + logger.error( + f"Y dimension in {foundBlockParameter.name} do not match description!" + ) break elif line.startswith("LANGNAME"): foundBlockParameter.description = self.parseString(line) @@ -215,19 +231,17 @@ def read(self, file) -> None: elif line.startswith("WERT"): parameters = self.parseBlockParameters(line) if len(parameters) != foundBlockParameter.x_dimension: - logger.error(f"X dimension in {foundBlockParameter.name} do not match description!") + logger.error( + f"X dimension in {foundBlockParameter.name} do not match description!" + ) foundBlockParameter.values.append(parameters) elif line.startswith("EINHEIT_W"): foundBlockParameter.unit = self.parseString(line) elif line.startswith("VAR"): foundBlockParameter.variants.update(self.parseVariant(line)) - elif line.startswith(comment_qualifier): - if foundBlockParameter.comment is None: - foundBlockParameter.comment = line[1:].strip() + os.linesep - else: - foundBlockParameter.comment += line[1:].strip() + os.linesep else: - logger.warning(f"Unknown parameter field: {line}") + if not line.startswith("*"): + logger.warning(f"Unknown parameter field: {line}") self._blockParameterList.append(foundBlockParameter) @@ -235,7 +249,9 @@ def read(self, file) -> None: elif line.startswith("KENNLINIE"): reMatch = re.search(r"KENNLINIE\s+(.*?)\s+(\d+)", line.strip()) foundCharacteristicLine = DcmCharacteristicLine(reMatch.group(1)) - foundCharacteristicLine.x_dimension = self.convertValue(reMatch.group(2)) + foundCharacteristicLine.x_dimension = self.convertValue( + reMatch.group(2) + ) parameters = [] stx = [] @@ -243,7 +259,9 @@ def read(self, file) -> None: line = f.readline().strip() if line.startswith("END"): if len(stx) != foundCharacteristicLine.x_dimension: - logger.error(f"X dimension in {foundCharacteristicLine.name} do not match description!") + logger.error( + f"X dimension in {foundCharacteristicLine.name} do not match description!" + ) if len(parameters) != foundCharacteristicLine.x_dimension: logger.error( f"Values dimension in {foundCharacteristicLine.name} do not match description!" @@ -253,7 +271,9 @@ def read(self, file) -> None: elif line.startswith("LANGNAME"): foundCharacteristicLine.description = self.parseString(line) elif line.startswith("DISPLAYNAME"): - foundCharacteristicLine.display_name = self.parseString(line) + foundCharacteristicLine.display_name = self.parseString( + line + ) elif line.startswith("FUNKTION"): foundCharacteristicLine.function = self.parseString(line) elif line.startswith("WERT"): @@ -265,26 +285,24 @@ def read(self, file) -> None: elif line.startswith("EINHEIT_X"): foundCharacteristicLine.unit_x = self.parseString(line) elif line.startswith("VAR"): - foundCharacteristicLine.variants.update(self.parseVariant(line)) - elif line.startswith(comment_qualifier): - reMatch = re.search(r"SSTX\s+(.*)", line) - if reMatch: - foundCharacteristicLine.x_mapping = reMatch.group(1) - else: - if foundCharacteristicLine.comment is None: - foundCharacteristicLine.comment = line[1:].strip() + os.linesep - else: - foundCharacteristicLine.comment += line[1:].strip() + os.linesep + foundCharacteristicLine.variants.update( + self.parseVariant(line) + ) else: - logger.warning(f"Unknown parameter field: {line}") + if not line.startswith("*"): + logger.warning(f"Unknown parameter field: {line}") self._characteristicLineList.append(foundCharacteristicLine) # Check if fixed characteristic line elif line.startswith("FESTKENNLINIE"): reMatch = re.search(r"FESTKENNLINIE\s+(.*?)\s+(\d+)", line.strip()) - foundFixedCharacteristicLine = DcmFixedCharacteristicLine(reMatch.group(1)) - foundFixedCharacteristicLine.x_dimension = self.convertValue(reMatch.group(2)) + foundFixedCharacteristicLine = DcmFixedCharacteristicLine( + reMatch.group(1) + ) + foundFixedCharacteristicLine.x_dimension = self.convertValue( + reMatch.group(2) + ) parameters = [] stx = [] @@ -295,54 +313,72 @@ def read(self, file) -> None: logger.error( f"X dimension in {foundFixedCharacteristicLine.name} do not match description!" ) - if len(parameters) != foundFixedCharacteristicLine.x_dimension: + if ( + len(parameters) + != foundFixedCharacteristicLine.x_dimension + ): logger.error( f"Values dimension in {foundFixedCharacteristicLine.name} do not match description!" ) - foundFixedCharacteristicLine.values = dict(zip(stx, parameters)) + foundFixedCharacteristicLine.values = dict( + zip(stx, parameters) + ) break elif line.startswith("LANGNAME"): - foundFixedCharacteristicLine.description = self.parseString(line) + foundFixedCharacteristicLine.description = self.parseString( + line + ) elif line.startswith("DISPLAYNAME"): - foundFixedCharacteristicLine.display_name = self.parseString(line) + foundFixedCharacteristicLine.display_name = ( + self.parseString(line) + ) elif line.startswith("FUNKTION"): - foundFixedCharacteristicLine.function = self.parseString(line) + foundFixedCharacteristicLine.function = self.parseString( + line + ) elif line.startswith("WERT"): parameters.extend(self.parseBlockParameters(line)) elif line.startswith("ST/X"): stx.extend(self.parseBlockParameters(line)) elif line.startswith("EINHEIT_W"): - foundFixedCharacteristicLine.unit_values = self.parseString(line) + foundFixedCharacteristicLine.unit_values = self.parseString( + line + ) elif line.startswith("EINHEIT_X"): foundFixedCharacteristicLine.unit_x = self.parseString(line) elif line.startswith("VAR"): - foundFixedCharacteristicLine.variants.update(self.parseVariant(line)) - elif line.startswith(comment_qualifier): - reMatch = re.search(r"SSTX\s+(.*)", line) - if reMatch: - foundFixedCharacteristicLine.x_mapping = reMatch.group(1) - else: - if foundFixedCharacteristicLine.comment is None: - foundFixedCharacteristicLine.comment = line[1:].strip() + os.linesep - else: - foundFixedCharacteristicLine.comment += line[1:].strip() + os.linesep + foundFixedCharacteristicLine.variants.update( + self.parseVariant(line) + ) else: - logger.warning(f"Unknown parameter field: {line}") + if not line.startswith("*"): + logger.warning(f"Unknown parameter field: {line}") - self._fixedCharacteristicLineList.append(foundFixedCharacteristicLine) + self._fixedCharacteristicLineList.append( + foundFixedCharacteristicLine + ) # Check if group characteristic line elif line.startswith("GRUPPENKENNLINIE"): - reMatch = re.search(r"GRUPPENKENNLINIE\s+(.*?)\s+(\d+)", line.strip()) - foundGroupCharacteristicLine = DcmGroupCharacteristicLine(reMatch.group(1)) - foundGroupCharacteristicLine.x_dimension = self.convertValue(reMatch.group(2)) + reMatch = re.search( + r"GRUPPENKENNLINIE\s+(.*?)\s+(\d+)", line.strip() + ) + foundGroupCharacteristicLine = DcmGroupCharacteristicLine( + reMatch.group(1) + ) + foundGroupCharacteristicLine.x_dimension = self.convertValue( + reMatch.group(2) + ) parameters = [] stx = [] while True: line = f.readline().strip() if line.startswith("END"): - if len(parameters) != foundGroupCharacteristicLine.x_dimension: + if ( + len(parameters) + != foundGroupCharacteristicLine.x_dimension + ): logger.error( f"Values dimension in {foundGroupCharacteristicLine.name} do not match description!" ) @@ -350,119 +386,202 @@ def read(self, file) -> None: logger.error( f"X dimension in {foundGroupCharacteristicLine.name} do not match description!" ) - foundGroupCharacteristicLine.values = dict(zip(stx, parameters)) + foundGroupCharacteristicLine.values = dict( + zip(stx, parameters) + ) break elif line.startswith("LANGNAME"): - foundGroupCharacteristicLine.description = self.parseString(line) + foundGroupCharacteristicLine.description = self.parseString( + line + ) elif line.startswith("DISPLAYNAME"): - foundGroupCharacteristicLine.display_name = self.parseString(line) + foundGroupCharacteristicLine.display_name = ( + self.parseString(line) + ) elif line.startswith("FUNKTION"): - foundGroupCharacteristicLine.function = self.parseString(line) + foundGroupCharacteristicLine.function = self.parseString( + line + ) elif line.startswith("WERT"): parameters.extend(self.parseBlockParameters(line)) elif line.startswith("ST/X"): stx.extend(self.parseBlockParameters(line)) elif line.startswith("EINHEIT_W"): - foundGroupCharacteristicLine.unit_values = self.parseString(line) + foundGroupCharacteristicLine.unit_values = self.parseString( + line + ) elif line.startswith("EINHEIT_X"): foundGroupCharacteristicLine.unit_x = self.parseString(line) elif line.startswith("VAR"): - foundGroupCharacteristicLine.variants.update(self.parseVariant(line)) - elif line.startswith(comment_qualifier): - reMatch = re.search(r"SSTX\s+(.*)", line) - if reMatch: - foundGroupCharacteristicLine.x_mapping = reMatch.group(1) - else: - if foundGroupCharacteristicLine.comment is None: - foundGroupCharacteristicLine.comment = line[1:].strip() + os.linesep - else: - foundGroupCharacteristicLine.comment += line[1:].strip() + os.linesep + foundGroupCharacteristicLine.variants.update( + self.parseVariant(line) + ) else: - logger.warning(f"Unknown parameter field: {line}") + if not line.startswith("*"): + logger.warning(f"Unknown parameter field: {line}") - self._groupCharacteristicLineList.append(foundGroupCharacteristicLine) + self._groupCharacteristicLineList.append( + foundGroupCharacteristicLine + ) # Check for characteristic map elif line.startswith("KENNFELD "): - reMatch = re.search(r"KENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip()) + # reMatch = re.search(r"KENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip()) + # foundCharacteristicMap = DcmCharacteristicMap(reMatch.group(1)) + # # foundCharacteristicMap.x_dimension = self.convertValue(reMatch.group(2)) + # # foundCharacteristicMap.y_dimension = self.convertValue(reMatch.group(3)) + # stx = [] + # sty = None + + # while True: + # line = f.readline().strip() + # if line.startswith("END"): + # if len(foundCharacteristicMap.values) != foundCharacteristicMap.y_dimension: + # logger.error( + # f"Values dimension in {foundCharacteristicMap.name} does not match description!" + # ) + # if len(stx) != foundCharacteristicMap.x_dimension: + # logger.error(f"X dimension in {foundCharacteristicMap.name} do not match description!") + # for name, entry in foundCharacteristicMap.values.items(): + # if len(entry) != foundCharacteristicMap.x_dimension: + # logger.error( + # f"Values dimension in {foundCharacteristicMap.name} does not match description!" + # ) + # else: + # foundCharacteristicMap.values[name] = dict(zip(stx, entry)) + # break + # elif line.startswith("LANGNAME"): + # foundCharacteristicMap.description = self.parseString(line) + # elif line.startswith("DISPLAYNAME"): + # foundCharacteristicMap.display_name = self.parseString(line) + # elif line.startswith("FUNKTION"): + # foundCharacteristicMap.function = self.parseString(line) + # elif line.startswith("WERT"): + # if stx is None or sty is None: + # raise ValueError(f"Values before stx/sty in {foundCharacteristicMap.name}") + # parameters = self.parseBlockParameters(line) + # if sty not in foundCharacteristicMap.values.keys(): + # foundCharacteristicMap.values[sty] = [] + # foundCharacteristicMap.values[sty].extend(parameters) + # elif line.startswith("ST/X"): + # stx.extend(self.parseBlockParameters(line)) + # elif line.startswith("ST/Y"): + # sty = self.convertValue(line.split(" ", 1)[1].strip()) + # elif line.startswith("EINHEIT_W"): + # foundCharacteristicMap.unit_values = self.parseString(line) + # elif line.startswith("EINHEIT_X"): + # foundCharacteristicMap.unit_x = self.parseString(line) + # elif line.startswith("EINHEIT_Y"): + # foundCharacteristicMap.unit_y = self.parseString(line) + # elif line.startswith("VAR"): + # foundCharacteristicMap.variants.update(self.parseVariant(line)) + # else: + # if not line.startswith("*"): + # logger.warning(f"Unknown parameter field: {line}") + + reMatch = re.search( + r"KENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip() + ) foundCharacteristicMap = DcmCharacteristicMap(reMatch.group(1)) - foundCharacteristicMap.x_dimension = self.convertValue(reMatch.group(2)) - foundCharacteristicMap.y_dimension = self.convertValue(reMatch.group(3)) + x_dimension = self.convertValue(reMatch.group(2)) + y_dimension = self.convertValue(reMatch.group(3)) stx = [] sty = None + stys = [] + _values = dict() while True: line = f.readline().strip() if line.startswith("END"): - if len(foundCharacteristicMap.values) != foundCharacteristicMap.y_dimension: + if len(_values) != y_dimension: logger.error( f"Values dimension in {foundCharacteristicMap.name} does not match description!" ) - if len(stx) != foundCharacteristicMap.x_dimension: - logger.error(f"X dimension in {foundCharacteristicMap.name} do not match description!") - for name, entry in foundCharacteristicMap.values.items(): - if len(entry) != foundCharacteristicMap.x_dimension: + if len(stx) != x_dimension: + logger.error( + f"X dimension in {foundCharacteristicMap.name} do not match description!" + ) + for name, entry in _values.items(): + if len(entry) != x_dimension: logger.error( f"Values dimension in {foundCharacteristicMap.name} does not match description!" ) - else: - foundCharacteristicMap.values[name] = dict(zip(stx, entry)) + + foundCharacteristicMap.values = list(_values.values()) + foundCharacteristicMap.coords = (stx, stys) break elif line.startswith("LANGNAME"): - foundCharacteristicMap.description = self.parseString(line) + foundCharacteristicMap.attrs[ + "description" + ] = self.parseString(line) elif line.startswith("DISPLAYNAME"): - foundCharacteristicMap.display_name = self.parseString(line) + foundCharacteristicMap.attrs[ + "display_name" + ] = self.parseString(line) elif line.startswith("FUNKTION"): - foundCharacteristicMap.function = self.parseString(line) + foundCharacteristicMap.attrs["function"] = self.parseString( + line + ) elif line.startswith("WERT"): - if stx is None or sty is None: - raise ValueError(f"Values before stx/sty in {foundCharacteristicMap.name}") + if not (stx or stys): + raise ValueError( + f"Values before stx/sty in {foundCharacteristicMap.name}" + ) parameters = self.parseBlockParameters(line) - if sty not in foundCharacteristicMap.values.keys(): - foundCharacteristicMap.values[sty] = [] - foundCharacteristicMap.values[sty].extend(parameters) + if sty not in _values.keys(): + _values[sty] = [] + _values[sty].extend(parameters) elif line.startswith("ST/X"): stx.extend(self.parseBlockParameters(line)) elif line.startswith("ST/Y"): sty = self.convertValue(line.split(" ", 1)[1].strip()) + stys.append(sty) elif line.startswith("EINHEIT_W"): - foundCharacteristicMap.unit_values = self.parseString(line) + foundCharacteristicMap.attrs["units"] = self.parseString( + line + ) elif line.startswith("EINHEIT_X"): - foundCharacteristicMap.unit_x = self.parseString(line) + foundCharacteristicMap.attrs["units_x"] = self.parseString( + line + ) elif line.startswith("EINHEIT_Y"): - foundCharacteristicMap.unit_y = self.parseString(line) + foundCharacteristicMap.attrs["units_y"] = self.parseString( + line + ) elif line.startswith("VAR"): - foundCharacteristicMap.variants.update(self.parseVariant(line)) - elif line.startswith(comment_qualifier): - reMatchX = re.search(r"SSTX\s+(.*)", line) - reMatchY = re.search(r"SSTY\s+(.*)", line) - if reMatchX: - foundCharacteristicMap.x_mapping = reMatchX.group(1) - elif reMatchY: - foundCharacteristicMap.y_mapping = reMatchY.group(1) - else: - if foundCharacteristicMap.comment is None: - foundCharacteristicMap.comment = line[1:].strip() + os.linesep - else: - foundCharacteristicMap.comment += line[1:].strip() + os.linesep + foundCharacteristicMap.attrs["variants"].update( + self.parseVariant(line) + ) else: - logger.warning(f"Unknown parameter field: {line}") + if not line.startswith("*"): + logger.warning(f"Unknown parameter field: {line}") self._characteristicMapList.append(foundCharacteristicMap) # Check for fixed characteristic map elif line.startswith("FESTKENNFELD "): - reMatch = re.search(r"FESTKENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip()) - foundFixedCharacteristicMap = DcmFixedCharacteristicMap(reMatch.group(1)) - foundFixedCharacteristicMap.x_dimension = self.convertValue(reMatch.group(2)) - foundFixedCharacteristicMap.y_dimension = self.convertValue(reMatch.group(3)) + reMatch = re.search( + r"FESTKENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip() + ) + foundFixedCharacteristicMap = DcmFixedCharacteristicMap( + reMatch.group(1) + ) + foundFixedCharacteristicMap.x_dimension = self.convertValue( + reMatch.group(2) + ) + foundFixedCharacteristicMap.y_dimension = self.convertValue( + reMatch.group(3) + ) stx = [] sty = None while True: line = f.readline().strip() if line.startswith("END"): - if len(foundFixedCharacteristicMap.values) != foundFixedCharacteristicMap.y_dimension: + if ( + len(foundFixedCharacteristicMap.values) + != foundFixedCharacteristicMap.y_dimension + ): logger.error( f"Values dimension in {foundFixedCharacteristicMap.name} does not match description!" ) @@ -470,23 +589,39 @@ def read(self, file) -> None: logger.error( f"X dimension in {foundFixedCharacteristicMap.name} do not match description!" ) - for name, entry in foundFixedCharacteristicMap.values.items(): - if len(entry) != foundFixedCharacteristicMap.x_dimension: + for ( + name, + entry, + ) in foundFixedCharacteristicMap.values.items(): + if ( + len(entry) + != foundFixedCharacteristicMap.x_dimension + ): logger.error( f"Values dimension in {foundFixedCharacteristicMap.name} does not match description!" ) else: - foundFixedCharacteristicMap.values[name] = dict(zip(stx, entry)) + foundFixedCharacteristicMap.values[name] = dict( + zip(stx, entry) + ) break elif line.startswith("LANGNAME"): - foundFixedCharacteristicMap.description = self.parseString(line) + foundFixedCharacteristicMap.description = self.parseString( + line + ) elif line.startswith("DISPLAYNAME"): - foundFixedCharacteristicMap.display_name = self.parseString(line) + foundFixedCharacteristicMap.display_name = self.parseString( + line + ) elif line.startswith("FUNKTION"): - foundFixedCharacteristicMap.function = self.parseString(line) + foundFixedCharacteristicMap.function = self.parseString( + line + ) elif line.startswith("WERT"): if stx is None or sty is None: - raise ValueError(f"Values before stx/sty in {foundFixedCharacteristicMap.name}") + raise ValueError( + f"Values before stx/sty in {foundFixedCharacteristicMap.name}" + ) parameters = self.parseBlockParameters(line) if sty not in foundFixedCharacteristicMap.values.keys(): foundFixedCharacteristicMap.values[sty] = [] @@ -496,103 +631,197 @@ def read(self, file) -> None: elif line.startswith("ST/Y"): sty = self.convertValue(line.split(" ", 1)[1].strip()) elif line.startswith("EINHEIT_W"): - foundFixedCharacteristicMap.unit_values = self.parseString(line) + foundFixedCharacteristicMap.unit_values = self.parseString( + line + ) elif line.startswith("EINHEIT_X"): foundFixedCharacteristicMap.unit_x = self.parseString(line) elif line.startswith("EINHEIT_Y"): foundFixedCharacteristicMap.unit_y = self.parseString(line) elif line.startswith("VAR"): - foundFixedCharacteristicMap.variants.update(self.parseVariant(line)) - elif line.startswith(comment_qualifier): - reMatchX = re.search(r"SSTX\s+(.*)", line) - reMatchY = re.search(r"SSTY\s+(.*)", line) - if reMatchX: - foundFixedCharacteristicMap.x_mapping = reMatchX.group(1) - elif reMatchY: - foundFixedCharacteristicMap.y_mapping = reMatchY.group(1) - else: - if foundFixedCharacteristicMap.comment is None: - foundFixedCharacteristicMap.comment = line[1:].strip() + os.linesep - else: - foundFixedCharacteristicMap.comment += line[1:].strip() + os.linesep + foundFixedCharacteristicMap.variants.update( + self.parseVariant(line) + ) else: - logger.warning(f"Unknown parameter field: {line}") + if not line.startswith("*"): + logger.warning(f"Unknown parameter field: {line}") self._fixedCharacteristicMapList.append(foundFixedCharacteristicMap) # Check for group characteristic map elif line.startswith("GRUPPENKENNFELD "): - reMatch = re.search(r"GRUPPENKENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip()) - foundGroupCharacteristicMap = DcmGroupCharacteristicMap(reMatch.group(1)) - foundGroupCharacteristicMap.x_dimension = self.convertValue(reMatch.group(2)) - foundGroupCharacteristicMap.y_dimension = self.convertValue(reMatch.group(3)) + # reMatch = re.search( + # r"GRUPPENKENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip() + # ) + # foundGroupCharacteristicMap = DcmGroupCharacteristicMap( + # reMatch.group(1) + # ) + # foundGroupCharacteristicMap.x_dimension = self.convertValue( + # reMatch.group(2) + # ) + # foundGroupCharacteristicMap.y_dimension = self.convertValue( + # reMatch.group(3) + # ) + # stx = [] + # sty = None + + # while True: + # line = f.readline().strip() + # if line.startswith("END"): + # if ( + # len(foundGroupCharacteristicMap.values) + # != foundGroupCharacteristicMap.y_dimension + # ): + # logger.error( + # f"Values dimension in {foundGroupCharacteristicMap.name} does not match description!" + # ) + # if len(stx) != foundGroupCharacteristicMap.x_dimension: + # logger.error( + # f"X dimension in {foundGroupCharacteristicMap.name} do not match description!" + # ) + # for ( + # name, + # entry, + # ) in foundGroupCharacteristicMap.values.items(): + # if ( + # len(entry) + # != foundGroupCharacteristicMap.x_dimension + # ): + # logger.error( + # f"Values dimension in {foundGroupCharacteristicMap.name} does not match description!" + # ) + # else: + # foundGroupCharacteristicMap.values[name] = dict( + # zip(stx, entry) + # ) + # break + # elif line.startswith("LANGNAME"): + # foundGroupCharacteristicMap.description = self.parseString( + # line + # ) + # elif line.startswith("DISPLAYNAME"): + # foundGroupCharacteristicMap.display_name = self.parseString( + # line + # ) + # elif line.startswith("FUNKTION"): + # foundGroupCharacteristicMap.function = self.parseString( + # line + # ) + # elif line.startswith("WERT"): + # if stx is None or sty is None: + # raise ValueError( + # f"Values before stx/sty in {foundGroupCharacteristicMap.name}" + # ) + # parameters = self.parseBlockParameters(line) + # if sty not in foundGroupCharacteristicMap.values.keys(): + # foundGroupCharacteristicMap.values[sty] = [] + # foundGroupCharacteristicMap.values[sty].extend(parameters) + # elif line.startswith("ST/X"): + # stx.extend(self.parseBlockParameters(line)) + # elif line.startswith("ST/Y"): + # sty = self.convertValue(line.split(" ", 1)[1].strip()) + # elif line.startswith("EINHEIT_W"): + # foundGroupCharacteristicMap.unit_values = self.parseString( + # line + # ) + # elif line.startswith("EINHEIT_X"): + # foundGroupCharacteristicMap.unit_x = self.parseString(line) + # elif line.startswith("EINHEIT_Y"): + # foundGroupCharacteristicMap.unit_y = self.parseString(line) + # elif line.startswith("VAR"): + # foundGroupCharacteristicMap.variants.update( + # self.parseVariant(line) + # ) + # else: + # if not line.startswith("*"): + # logger.warning(f"Unknown parameter field: {line}") + + reMatch = re.search( + r"GRUPPENKENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip() + ) + foundGroupCharacteristicMap = DcmGroupCharacteristicMap( + reMatch.group(1) + ) + x_dimension = self.convertValue(reMatch.group(2)) + y_dimension = self.convertValue(reMatch.group(3)) stx = [] sty = None + stys = [] + _values = dict() while True: line = f.readline().strip() if line.startswith("END"): - if len(foundGroupCharacteristicMap.values) != foundGroupCharacteristicMap.y_dimension: + if len(_values) != y_dimension: logger.error( f"Values dimension in {foundGroupCharacteristicMap.name} does not match description!" ) - if len(stx) != foundGroupCharacteristicMap.x_dimension: + if len(stx) != x_dimension: logger.error( f"X dimension in {foundGroupCharacteristicMap.name} do not match description!" ) - for name, entry in foundGroupCharacteristicMap.values.items(): - if len(entry) != foundGroupCharacteristicMap.x_dimension: + for name, entry in _values.items(): + if len(entry) != x_dimension: logger.error( f"Values dimension in {foundGroupCharacteristicMap.name} does not match description!" ) - else: - foundGroupCharacteristicMap.values[name] = dict(zip(stx, entry)) + + foundGroupCharacteristicMap.values = list(_values.values()) + foundGroupCharacteristicMap.coords = (stx, stys) break elif line.startswith("LANGNAME"): - foundGroupCharacteristicMap.description = self.parseString(line) + foundGroupCharacteristicMap.attrs[ + "description" + ] = self.parseString(line) elif line.startswith("DISPLAYNAME"): - foundGroupCharacteristicMap.display_name = self.parseString(line) + foundGroupCharacteristicMap.attrs[ + "display_name" + ] = self.parseString(line) elif line.startswith("FUNKTION"): - foundGroupCharacteristicMap.function = self.parseString(line) + foundGroupCharacteristicMap.attrs[ + "function" + ] = self.parseString(line) elif line.startswith("WERT"): - if stx is None or sty is None: - raise ValueError(f"Values before stx/sty in {foundGroupCharacteristicMap.name}") + if not (stx or stys): + raise ValueError( + f"Values before stx/sty in {foundGroupCharacteristicMap.name}" + ) parameters = self.parseBlockParameters(line) - if sty not in foundGroupCharacteristicMap.values.keys(): - foundGroupCharacteristicMap.values[sty] = [] - foundGroupCharacteristicMap.values[sty].extend(parameters) + if sty not in _values.keys(): + _values[sty] = [] + _values[sty].extend(parameters) elif line.startswith("ST/X"): stx.extend(self.parseBlockParameters(line)) elif line.startswith("ST/Y"): sty = self.convertValue(line.split(" ", 1)[1].strip()) + stys.append(sty) elif line.startswith("EINHEIT_W"): - foundGroupCharacteristicMap.unit_values = self.parseString(line) + foundGroupCharacteristicMap.attrs[ + "units" + ] = self.parseString(line) elif line.startswith("EINHEIT_X"): - foundGroupCharacteristicMap.unit_x = self.parseString(line) + foundGroupCharacteristicMap.attrs[ + "units_x" + ] = self.parseString(line) elif line.startswith("EINHEIT_Y"): - foundGroupCharacteristicMap.unit_y = self.parseString(line) + foundGroupCharacteristicMap.attrs[ + "units_y" + ] = self.parseString(line) elif line.startswith("VAR"): - foundGroupCharacteristicMap.variants.update(self.parseVariant(line)) - elif line.startswith(comment_qualifier): - reMatchX = re.search(r"SSTX\s+(.*)", line) - reMatchY = re.search(r"SSTY\s+(.*)", line) - if reMatchX: - foundGroupCharacteristicMap.x_mapping = reMatchX.group(1) - elif reMatchY: - foundGroupCharacteristicMap.y_mapping = reMatchY.group(1) - else: - if foundGroupCharacteristicMap.comment is None: - foundGroupCharacteristicMap.comment = line[1:].strip() + os.linesep - else: - foundGroupCharacteristicMap.comment += line[1:].strip() + os.linesep + foundGroupCharacteristicMap.attrs["variants"].update( + self.parseVariant(line) + ) else: - logger.warning(f"Unknown parameter field: {line}") + if not line.startswith("*"): + logger.warning(f"Unknown parameter field: {line}") self._groupCharacteristicMapList.append(foundGroupCharacteristicMap) # Check if distribution elif line.startswith("STUETZSTELLENVERTEILUNG"): - reMatch = re.search(r"STUETZSTELLENVERTEILUNG\s+(.*?)\s+(\d+)", line.strip()) + reMatch = re.search( + r"STUETZSTELLENVERTEILUNG\s+(.*?)\s+(\d+)", line.strip() + ) foundDistribution = DcmDistribution(reMatch.group(1)) foundDistribution.x_dimension = self.convertValue(reMatch.group(2)) parameters = None @@ -601,8 +830,13 @@ def read(self, file) -> None: while True: line = f.readline().strip() if line.startswith("END"): - if len(foundDistribution.values) != foundDistribution.x_dimension: - logger.error(f"X dimension in {foundDistribution.name} do not match description!") + if ( + len(foundDistribution.values) + != foundDistribution.x_dimension + ): + logger.error( + f"X dimension in {foundDistribution.name} do not match description!" + ) break elif line.startswith("LANGNAME"): foundDistribution.description = self.parseString(line) @@ -611,18 +845,16 @@ def read(self, file) -> None: elif line.startswith("FUNKTION"): foundDistribution.function = self.parseString(line) elif line.startswith("ST/X"): - foundDistribution.values.extend(self.parseBlockParameters(line)) + foundDistribution.values.extend( + self.parseBlockParameters(line) + ) elif line.startswith("EINHEIT_X"): foundDistribution.unit_x = self.parseString(line) elif line.startswith("VAR"): foundDistribution.variants.update(self.parseVariant(line)) - elif line.startswith(comment_qualifier): - if foundDistribution.comment is None: - foundDistribution.comment = line[1:].strip() + os.linesep - else: - foundDistribution.comment += line[1:].strip() + os.linesep else: - logger.warning(f"Unknown parameter field: {line}") + if not line.startswith("*"): + logger.warning(f"Unknown parameter field: {line}") self._distributionList.append(foundDistribution) From 427e1e7b0c1b6b3942a2975223ccfccfdccdefe0 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Mon, 2 Jan 2023 17:11:36 +0100 Subject: [PATCH 05/28] Update dcm_characteristic_map.py --- src/dcmReader/dcm_characteristic_map.py | 95 ++++++++----------------- 1 file changed, 31 insertions(+), 64 deletions(-) diff --git a/src/dcmReader/dcm_characteristic_map.py b/src/dcmReader/dcm_characteristic_map.py index bcf476c..a48d769 100644 --- a/src/dcmReader/dcm_characteristic_map.py +++ b/src/dcmReader/dcm_characteristic_map.py @@ -1,9 +1,12 @@ """ Definition of DCM characteristic map """ +from __future__ import annotations +from dcmReader.utils import _DcmBase -class DcmCharacteristicMap: + +class DcmCharacteristicMap(_DcmBase): """Definition of a characteristic map Attributes: @@ -21,68 +24,32 @@ class DcmCharacteristicMap: contains the values from ST/Y. x_dimension (int): Dimension in x direction of the characteristic maps y_dimension (int): Dimension in y direction of the characteristic maps - x_mapping (str): Mapping of the x axis to a distribution, if available as a comment in DCM - y_mapping (str): Mapping of the y axis to a distribution, if available as a comment in DCM - comment (str): Block comment """ - def __init__(self, name) -> None: - self.name = name - self.values = dict() - self.description = None - self.display_name = None - self.variants = dict() - self.function = None - self.unit_x = None - self.unit_y = None - self.unit_values = None - self.x_dimension = 0 - self.y_dimension = 0 - self.x_mapping = None - self.y_mapping = None - self.comment = None - self._type_name = "KENNFELD" - - def __str__(self): - value = f"{self._type_name} {self.name} {self.x_dimension} {self.y_dimension}\n" - - if self.comment: - for line in self.comment.splitlines(True): - value += f"* {line}" - if self.description: - value += f' LANGNAME "{self.description}"\n' - if self.function: - value += f' FUNKTION "{self.function}"\n' - if self.display_name: - value += f" DISPLAYNAME {self.display_name}\n" - if self.unit_x: - value += f' EINHEIT_X "{self.unit_x}"\n' - if self.unit_y: - value += f' EINHEIT_Y "{self.unit_y}"\n' - if self.unit_values: - value += f' EINHEIT_W "{self.unit_values}"\n' - if self.x_mapping: - value += f'*SSTX {self.x_mapping}\n' - if self.y_mapping: - value += f'*SSTY {self.y_mapping}\n' - stx_written = False - for y_entry, map_values in self.values.items(): - x_entries = "" - value_entries = "" - for x_entry, value_entry in map_values.items(): - x_entries += f"{str(x_entry)} " - value_entries += f"{str(value_entry)} " - if not stx_written: - value += f' ST/X {x_entries.strip()}\n' - stx_written = True - value += f' ST/Y {str(y_entry).strip()}\n' - value += f' WERT {value_entries.strip()}\n' - for var_name, var_value in self.variants.items(): - value += f" VAR {var_name}={var_value}\n" - - value += "END" - - return value - - def __lt__(self, other): - return self.function < other.function and self.description < other.description + def __init__( + self, + name: str, + values: list[list[float]] | None = None, + coords: tuple[list[float], ...] = None, + attrs: dict | None = None, + *, + block_type: str = "KENNFELD", + ) -> None: + if attrs is None: + attrs = { + "description": "", + "display_name": "", + "variants": {}, + "function": "", + "units_x": "", + "units_y": "", + "units": "", + } + + super().__init__( + name=name, + values=values, + coords=coords, + attrs=attrs, + block_type=block_type, + ) From b1304daa99d6a147d85b9e389d81ba92e302a7a4 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Mon, 2 Jan 2023 17:12:26 +0100 Subject: [PATCH 06/28] Update dcm_fixed_characteristic_map.py --- src/dcmReader/dcm_fixed_characteristic_map.py | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/dcmReader/dcm_fixed_characteristic_map.py b/src/dcmReader/dcm_fixed_characteristic_map.py index e891cd9..f5ad091 100644 --- a/src/dcmReader/dcm_fixed_characteristic_map.py +++ b/src/dcmReader/dcm_fixed_characteristic_map.py @@ -1,6 +1,7 @@ """ Definition of DCM fixed characteristic map """ +from __future__ import annotations from dcmReader.dcm_characteristic_map import DcmCharacteristicMap @@ -8,9 +9,30 @@ class DcmFixedCharacteristicMap(DcmCharacteristicMap): """Definition of a fixed characteristic map, derived from characteristic map""" - def __init__(self, name) -> None: - super().__init__(name) - self._type_name = "FESTKENNFELD" + def __init__( + self, + name: str, + values: list[list[float]] | None = None, + coords: tuple[list[float], ...] = None, + attrs: dict | None = None, + *, + block_type: str = "FESTKENNFELD", + ) -> None: + if attrs is None: + attrs = { + "description": "", + "display_name": "", + "variants": {}, + "function": "", + "units_x": "", + "units_y": "", + "units": "", + } - def __lt__(self, other): - return self.function < other.function and self.description < other.description + super().__init__( + name=name, + values=values, + coords=coords, + attrs=attrs, + block_type=block_type, + ) From daf07daf1f49233df1d2e17fedfdf5f08a26b6c0 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Mon, 2 Jan 2023 17:13:13 +0100 Subject: [PATCH 07/28] Update dcm_group_characteristic_map.py --- src/dcmReader/dcm_group_characteristic_map.py | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/dcmReader/dcm_group_characteristic_map.py b/src/dcmReader/dcm_group_characteristic_map.py index f3d0673..0a6d517 100644 --- a/src/dcmReader/dcm_group_characteristic_map.py +++ b/src/dcmReader/dcm_group_characteristic_map.py @@ -1,6 +1,7 @@ """ Definition of DCM fixed characteristic map """ +from __future__ import annotations from dcmReader.dcm_characteristic_map import DcmCharacteristicMap @@ -8,9 +9,30 @@ class DcmGroupCharacteristicMap(DcmCharacteristicMap): """Definition of a group characteristic map, derived from characteristic map""" - def __init__(self, name) -> None: - super().__init__(name) - self._type_name = "GRUPPENKENNFELD" + def __init__( + self, + name: str, + values: list[list[float]] | None = None, + coords: tuple[list[float], ...] = None, + attrs: dict | None = None, + *, + block_type: str = "GRUPPENKENNFELD", + ) -> None: + if attrs is None: + attrs = { + "description": "", + "display_name": "", + "variants": {}, + "function": "", + "units_x": "", + "units_y": "", + "units": "", + } - def __lt__(self, other): - return self.function < other.function and self.description < other.description + super().__init__( + name=name, + values=values, + coords=coords, + attrs=attrs, + block_type=block_type, + ) From fe06da865e6bf626370488a8fbd37c672e1e2cf8 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 8 Jan 2023 11:23:57 +0100 Subject: [PATCH 08/28] Update dcm_reader.py --- src/dcmReader/dcm_reader.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 3550ab7..0f32b1c 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -408,8 +408,6 @@ def read(self, file) -> None: found_characteristic_map.y_dimension = self.convert_value(re_match.group(3)) stx = [] sty = None - stys = [] - _values = dict() while True: line = dcm_file.readline().strip() From 4defa65ff55c82c1e2a5cbd690d9d7ca0b71f873 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 8 Jan 2023 11:29:46 +0100 Subject: [PATCH 09/28] Update dcm_reader.py --- src/dcmReader/dcm_reader.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 0f32b1c..5f86c58 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -549,11 +549,11 @@ def read(self, file) -> None: elif line.startswith("GRUPPENKENNFELD "): re_match = re.search(r"GRUPPENKENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip()) - found_group_characteristic_map = foundGroupCharacteristicMap = DcmGroupCharacteristicMap( - reMatch.group(1) + found_group_characteristic_map = DcmGroupCharacteristicMap( + re_match.group(1) ) - x_dimension = self.convert_value(reMatch.group(2)) - y_dimension = self.convert_value(reMatch.group(3)) + x_dimension = self.convert_value(re_match.group(2)) + y_dimension = self.convert_value(re_match.group(3)) stx = [] sty = None @@ -565,18 +565,16 @@ def read(self, file) -> None: if line.startswith("END"): if len(_values) != y_dimension: logger.error( - "Values dimension in %s \ - does not match description!", found_group_characteristic_map.name + f"Values dimension in {found_group_characteristic_map.name} does not match description!" ) if len(stx) != x_dimension: logger.error( - "X dimension in %s do not match description!", found_group_characteristic_map.name + f"X dimension in {found_group_characteristic_map.name} do not match description!", ) for name, entry in _values.items(): if len(entry) != x_dimension: logger.error( - "Values dimension in %s \ - does not match description!", found_group_characteristic_map.name + "Values dimension in {found_group_characteristic_map.name} does not match description!", ) found_group_characteristic_map.values = list(_values.values()) From 184e5eecf171f649782ccca064e9cc839a1ee5d1 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 8 Jan 2023 12:17:13 +0100 Subject: [PATCH 10/28] Update dcm_reader.py --- src/dcmReader/dcm_reader.py | 54 +++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 5f86c58..a9af379 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -549,12 +549,10 @@ def read(self, file) -> None: elif line.startswith("GRUPPENKENNFELD "): re_match = re.search(r"GRUPPENKENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip()) - found_group_characteristic_map = DcmGroupCharacteristicMap( - re_match.group(1) - ) + found_group_characteristic_map = DcmGroupCharacteristicMap(re_match.group(1)) x_dimension = self.convert_value(re_match.group(2)) y_dimension = self.convert_value(re_match.group(3)) - + stx = [] sty = None stys = [] @@ -569,34 +567,26 @@ def read(self, file) -> None: ) if len(stx) != x_dimension: logger.error( - f"X dimension in {found_group_characteristic_map.name} do not match description!", + f"X dimension in {found_group_characteristic_map.name} do not match description!", ) for name, entry in _values.items(): if len(entry) != x_dimension: logger.error( - "Values dimension in {found_group_characteristic_map.name} does not match description!", + "Values dimension in {found_group_characteristic_map.name} does not match description!", ) found_group_characteristic_map.values = list(_values.values()) found_group_characteristic_map.coords = (stx, stys) break elif line.startswith("LANGNAME"): - found_group_characteristic_map.attrs[ - "description" - ] = self.parse_string(line) + found_group_characteristic_map.attrs["description"] = self.parse_string(line) elif line.startswith("DISPLAYNAME"): - found_group_characteristic_map.attrs[ - "display_name" - ] = self.parse_string(line) + found_group_characteristic_map.attrs["display_name"] = self.parse_string(line) elif line.startswith("FUNKTION"): - found_group_characteristic_map.attrs[ - "function" - ] = self.parse_string(line) + found_group_characteristic_map.attrs["function"] = self.parse_string(line) elif line.startswith("WERT"): if not (stx or stys): - raise ValueError( - f"Values before stx/sty in {found_group_characteristic_map.name}" - ) + raise ValueError(f"Values before stx/sty in {found_group_characteristic_map.name}") parameters = self.parse_block_parameters(line) if sty not in _values.keys(): _values[sty] = [] @@ -607,21 +597,25 @@ def read(self, file) -> None: sty = self.convert_value(line.split(" ", 1)[1].strip()) stys.append(sty) elif line.startswith("EINHEIT_W"): - found_group_characteristic_map.attrs[ - "units" - ] = self.parse_string(line) + found_group_characteristic_map.attrs["units"] = self.parse_string(line) elif line.startswith("EINHEIT_X"): - found_group_characteristic_map.attrs[ - "units_x" - ] = self.parse_string(line) + found_group_characteristic_map.attrs["units_x"] = self.parse_string(line) elif line.startswith("EINHEIT_Y"): - found_group_characteristic_map.attrs[ - "units_y" - ] = self.parse_string(line) + found_group_characteristic_map.attrs["units_y"] = self.parse_string(line) elif line.startswith("VAR"): - found_group_characteristic_map.attrs["variants"].update( - self.parse_variant(line) - ) + found_group_characteristic_map.attrs["variants"].update(self.parse_variant(line)) + elif line.startswith(comment_qualifier): + re_match_x = re.search(r"SSTX\s+(.*)", line) + re_match_y = re.search(r"SSTY\s+(.*)", line) + if re_match_x: + found_group_characteristic_map.attrs["x_mapping"] = re_match_x.group(1) + elif re_match_y: + found_group_characteristic_map.attrs["y_mapping"] = re_match_y.group(1) + else: + if found_group_characteristic_map.attrs["comment"] is None: + found_group_characteristic_map.attrs["comment"] = line[1:].strip() + os.linesep + else: + found_group_characteristic_map.attrs["comment"] += line[1:].strip() + os.linesep else: if not line.startswith("*"): logger.warning(f"Unknown parameter field: {line}") From a99b7667840db57b984652718e5a08cec8146764 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Tue, 10 Jan 2023 00:56:45 +0100 Subject: [PATCH 11/28] better dcm format printer --- src/dcmReader/dcm_reader.py | 2 +- src/dcmReader/utils.py | 221 ++++++------------------------------ 2 files changed, 33 insertions(+), 190 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index a9af379..23afe8f 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -576,7 +576,7 @@ def read(self, file) -> None: ) found_group_characteristic_map.values = list(_values.values()) - found_group_characteristic_map.coords = (stx, stys) + found_group_characteristic_map.coords = (stys, stx) break elif line.startswith("LANGNAME"): found_group_characteristic_map.attrs["description"] = self.parse_string(line) diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py index c75ebe6..5b0c77d 100644 --- a/src/dcmReader/utils.py +++ b/src/dcmReader/utils.py @@ -94,11 +94,10 @@ class _DcmBase(ShapeRelatedMixin): def __lt__(self, other): return ( - self.attrs["function"] < other.attrs["function"] - and self.attrs["description"] < other.attrs["description"] + self.attrs["function"] < other.attrs["function"] and self.attrs["description"] < other.attrs["description"] ) - def _print_str(self, name: str, is_function: False) -> str: + def _print_dcm_format(self, name: str, is_function: False) -> str: """ Print the data according to the dcm-format. @@ -112,188 +111,31 @@ def _print_str(self, name: str, is_function: False) -> str: Is the block a 'FUNKTIONEN'. """ - def chunked(val, n=6): - yield from [val[i : i + n] for i in range(0, len(val), n)] + def to_str(val: str | int | float | list, delimiter: str = " ") -> str: + val_list: list = [val] if isinstance(val, (str, int, float)) else val + return delimiter.join(map(str, val_list)) - def to_str(list_: str | int | float | list) -> str: - list__: list = [list_] if isinstance(list_, (str, int, float)) else list_ - return " ".join(map(str, list__)) + def print_values(key: str, value: str | int | float | list, n: int = 6) -> str: + """Print pairwise values prettily.""" + # Normalize so that val is always a list: + value_list: list = [value] if isinstance(value, (str, int, float)) else value - if is_function: - return f'{name} {self.name} "{self.version}" "{self.description}"' - - value = f"{name} {self.name} {to_str(self.shape)}\n" - - ks = ( - ("LANGNAME", "description", lambda x: f'"{x}"'), - ("FUNKTION", "function", lambda x: f"{x}"), - ("DISPLAYNAME", "display_name", lambda x: f'"{x}"'), - ("EINHEIT_X", "units_x", lambda x: f'"{x}"'), - ("EINHEIT_Y", "units_y", lambda x: f'"{x}"'), - ("EINHEIT_W", "units", lambda x: f'"{x}"'), - ) - for k, v, f in ks: - if self.attrs.get(v, ""): - value += f" {k: <13} {f(self.attrs[v])}\n" + # Split too long lists into chunks. + value_list_chunked = [value_list[i : i + n] for i in range(0, len(value_list), n)] - # ndim = self.ndim - # for i, (coord, coord_label) in enumerate(zip(self.coords, ("X", "Y"))): - # lbl = f"ST/{coord_label}" - # value += f" {lbl: <13} {to_str(coord)}\n" - - # if i == ndim - 1: - # for value_entries in chunked(self.values[i]): - # value += f" {'WERT': <13} {to_str(value_entries)}\n" - - for i, x_entries in enumerate(chunked(self.coords[0])): - value += f" {'ST/X': <13} {to_str(x_entries)}\n" - - for i, y_entry in enumerate(self.coords[1]): - value += f" {'ST/Y': <13} {y_entry}\n" - - for value_entries in chunked(self.values[i]): - value += f" {'WERT': <13} {to_str(value_entries)}\n" - - for var_name, var_value in self.attrs["variants"].items(): - value += f" {'VAR': <13} {var_name}={var_value}\n" - - value += "END" - - return value - - def __str__(self) -> str: - is_function = True if self.block_type == "FUNKTIONEN" else False - return self._print_str(self.block_type, is_function) - - -class _DcmBaseOld: - _name: str - _attrs: dict - _values: list[list[float]] - _coords: tuple[list[float], ...] - - __slots__ = ("_name", "_attrs", "_values", "_coords", "_block_type") - - def __init__( - self, - name: str, - values: list[list[float]] | None = None, - coords: tuple[list[float], ...] = None, - attrs: dict | None = None, - *, - block_type: str = "", - ): - self.name = name - self.values = [] if values is None else values - self.coords = () if coords is None else coords - self.attrs = {} if attrs is None else attrs - - self._block_type = block_type - - @property - def name(self) -> str: - return self._name - - @name.setter - def name(self, value: str) -> None: - self._name = value - - @property - def attrs(self) -> dict: - return self._attrs - - @attrs.setter - def attrs(self, value: dict) -> None: - self._attrs = value - - @property - def values(self) -> list[list[float]]: - """Return only values WERT, no x or y axis values.""" - return self._values - - @values.setter - def values(self, value: list[list[float]]) -> None: - self._values = value - - @property - def coords(self) -> tuple[list[float], ...]: - """Return x or y axis values.""" - return self._coords - - @coords.setter - def coords(self, value: tuple[list[float], ...]) -> None: - """Return x or y axis values.""" - self._coords = value - - @property - def shape(self) -> tuple[int, ...]: - """ - Get shape of array. - - See Also - -------- - numpy.ndarray.shape - """ - return _get_shape(self.values) - - @property - def ndim(self) -> int: - """ - Get number of array dimensions. - - See Also - -------- - numpy.ndarray.ndim - """ - return len(self.shape) - - @property - def size(self) -> int: - """ - Get number of elements in the array. - - See Also - -------- - numpy.ndarray.size - """ - return math.prod(self.shape) - - def __len__(self) -> int: - try: - return self.shape[0] - except IndexError: - raise TypeError("len() of unsized object") - - def __lt__(self, other): - return ( - self.attrs["function"] < other.attrs["function"] - and self.attrs["description"] < other.attrs["description"] - ) - - def _print_str(self, name: str, is_function: False) -> str: - """ - Print the data according to the dcm-format. - - Arrays longer than 6 are split to new line. - - Parameters - ---------- - name : str - Name of the block. - is_function : False - Is the block a 'FUNKTIONEN'. - """ - - def chunked(val, n=6): - yield from [val[i : i + n] for i in range(0, len(val), n)] - - def to_str(list_) -> str: - return " ".join(map(str, list_)) + out = "" + for v in value_list_chunked: + out += f" {key: <13} {to_str(v)}\n" + return out if is_function: - return f'{name} {self.name} "{self.version}" "{self.description}"' + return f'{name} {self.name} "{self.version}" "{self.description}"' + + shape_rev = list(reversed(self.shape)) + coords_rev = list(reversed(self.coords)) + ndim = self.ndim - value = f"{name} {self.name} {to_str(self.shape)}\n" + value = f"{name} {self.name} {to_str(shape_rev)}\n" ks = ( ("LANGNAME", "description", lambda x: f'"{x}"'), @@ -305,23 +147,24 @@ def to_str(list_) -> str: ) for k, v, f in ks: if self.attrs.get(v, ""): - value += f" {k: <13} {f(self.attrs[v])}\n" + value += print_values(k, f"{f(self.attrs[v])}") + + for i, val in enumerate(self.values): + if i == 0 and ndim > 0: + value += print_values("ST/X", coords_rev[0]) - for x_entries in chunked(self.coords[0]): - value += f" {'ST/X': <13} {to_str(x_entries)}\n" + if ndim > 1: + value += print_values("ST/Y", coords_rev[1][i]) - for y_entry, values in zip(self.coords[1], self.values): - value += f" {'ST/Y': <13} {str(y_entry)}\n" - for value_entries in chunked(values): - value += f" {'WERT': <13} {to_str(value_entries)}\n" + value += print_values("WERT", val) for var_name, var_value in self.attrs["variants"].items(): - value += f" {'VAR': <13} {var_name}={var_value}\n" + value += print_values("VAR", f"{var_name}={var_value}") value += "END" return value def __str__(self) -> str: - is_function = True if self._block_type == "FUNKTIONEN" else False - return self._print_str(self._block_type, is_function) + is_function = self.block_type == "FUNKTIONEN" + return self._print_dcm_format(self.block_type, is_function) From 0e2dbc0049369e658e01405876cddb6d2485c057 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 15 Jan 2023 22:44:48 +0100 Subject: [PATCH 12/28] Fixes for base class --- src/dcmReader/utils.py | 56 ++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py index 5b0c77d..bf3351f 100644 --- a/src/dcmReader/utils.py +++ b/src/dcmReader/utils.py @@ -4,10 +4,19 @@ from __future__ import annotations import math -from typing import Protocol +from typing import TYPE_CHECKING, Protocol +from abc import ABC, abstractmethod from dataclasses import dataclass, field +if TYPE_CHECKING: + try: + from numpy.typing import ArrayLike + except ImportError: + from typing import Union + + ArrayLike = Union[float, list[float], list[list[float]]] + def _get_shape(ndarray: list | float) -> tuple[int, ...]: """ @@ -39,11 +48,11 @@ def _get_shape(ndarray: list | float) -> tuple[int, ...]: return () -class HasValuesProtocol(Protocol): - values: list[list[float]] +class _HasValuesProtocol(Protocol): + values: ArrayLike -class ShapeRelatedMixin(HasValuesProtocol): +class ShapeRelatedMixin(_HasValuesProtocol): @property def shape(self) -> tuple[int, ...]: """ @@ -84,12 +93,26 @@ def __len__(self) -> int: raise TypeError("len() of unsized object") +def _attrs_init() -> dict: + return { + "description": "", + "display_name": "", + "variants": {}, + "text": "", + "function": "", + "units_x": "", + "units_y": "", + "units": "", + } + + @dataclass class _DcmBase(ShapeRelatedMixin): name: str - values: list[list[float]] = field(default_factory=list) - coords: tuple[list[float], ...] = field(default_factory=tuple) - attrs: dict = field(default_factory=dict) + values: ArrayLike = field(default_factory=list) + coords: tuple[ArrayLike, ...] = field(default_factory=tuple) + dims: tuple[str, ...] = field(default_factory=tuple) + attrs: dict = field(default_factory=_attrs_init) block_type: str = "" def __lt__(self, other): @@ -144,22 +167,29 @@ def print_values(key: str, value: str | int | float | list, n: int = 6) -> str: ("EINHEIT_X", "units_x", lambda x: f'"{x}"'), ("EINHEIT_Y", "units_y", lambda x: f'"{x}"'), ("EINHEIT_W", "units", lambda x: f'"{x}"'), + ("*SSTX", "x_mapping", lambda x: f"{x}"), + ("*SSTY", "y_mapping", lambda x: f"{x}"), + # Printed after WERT: + ("TEXT", "text", lambda x: f"{x}"), + ("VAR", "variants", lambda x: f"{x}"), ) - for k, v, f in ks: + idx_as_suffx = 2 + for k, v, f in ks[:-idx_as_suffx]: if self.attrs.get(v, ""): value += print_values(k, f"{f(self.attrs[v])}") - for i, val in enumerate(self.values): - if i == 0 and ndim > 0: - value += print_values("ST/X", coords_rev[0]) + if ndim > 0: + value += print_values("ST/X", coords_rev[0]) + for i, val in enumerate(self.values if ndim > 1 else [self.values]): if ndim > 1: value += print_values("ST/Y", coords_rev[1][i]) value += print_values("WERT", val) - for var_name, var_value in self.attrs["variants"].items(): - value += print_values("VAR", f"{var_name}={var_value}") + for k, v, f in ks[-idx_as_suffx:]: + if self.attrs.get(v, ""): + value += print_values(k, f"{f(self.attrs[v])}") value += "END" From 4518e690dfaa266c2433f72ef66775033403d153 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 15 Jan 2023 22:46:10 +0100 Subject: [PATCH 13/28] Use similar code paths for parsing the blocks. --- src/dcmReader/dcm_reader.py | 510 +++++++++++++++--------------------- 1 file changed, 218 insertions(+), 292 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 23afe8f..b86db7d 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -6,6 +6,8 @@ import re import logging +from collections import defaultdict + from dcmReader.dcm_parameter import DcmParameter from dcmReader.dcm_function import DcmFunction from dcmReader.dcm_parameter_block import DcmParameterBlock @@ -20,6 +22,8 @@ logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG) logger = logging.getLogger(__name__) +comment_qualifier = ("!", "*", ".") + class DcmReader: """Parser for the DCM (Data Conservation Format) format used by e.g. Vector, ETAS,...""" @@ -38,7 +42,43 @@ def __init__(self): self._group_characteristic_map_list = [] self._distribution_list = [] - def parse_variant(self, line): + self.parser_methods = { + "LANGNAME": {"key": "description", "method": self.parse_string}, + "DISPLAYNAME": {"key": "display_name", "method": self.parse_string}, + "FUNKTION": {"key": "function", "method": self.parse_string}, + "WERT": {"key": "", "method": self.parse_wert}, + "ST/X": {"key": "", "method": self.parse_coord_x}, + "ST/Y": {"key": "", "method": self.parse_coord_y}, + "EINHEIT_W": {"key": "units", "method": self.parse_string}, + "EINHEIT_X": {"key": "units_x", "method": self.parse_string}, + "EINHEIT_Y": {"key": "units_y", "method": self.parse_string}, + "VAR": {"key": "variants", "method": self.parse_string}, + "TEXT": {"key": "text", "method": self.parse_string}, + "*SSTX": {"key": "x_mapping", "method": self.parse_string}, + "*SSTY": {"key": "y_mapping", "method": self.parse_string}, + } + self.parser_methods.update({k: {"key": "", "method": self.parse_comment} for k in comment_qualifier}) + + def parse_wert(self, line: str, *, coord_x, coord_y, values: defaultdict, **kwargs): + # if not (coord_x or coord_y): + # raise ValueError(f"Values before stx/sty in {kwargs.get('name', 'Error')}") + + sty = coord_y[-1] if len(coord_y) > 0 else None + values[sty].extend(self.parse_block_parameters(line)) + + def parse_coord_x(self, line: str, *, coord_x: list, **kwargs) -> None: + coord_x.extend(self.parse_block_parameters(line)) + + def parse_coord_y(self, line: str, *, coord_y: list, **kwargs) -> None: + self.parse_coord_x(line, coord_x=coord_y) + + def parse_var(self, line: str, *, attrs: dict, key: str, **kwargs) -> None: + attrs[key].update(self.parse_variant(line)) + + def parse_comment(self, line: str, *, attrs: dict, **kwargs): + attrs["comment"] = attrs.get("comment", "") + line[1:].strip() + os.linesep + + def parse_variant(self, line, **kwargs): """Parses a variant field Args: @@ -57,7 +97,7 @@ def parse_variant(self, line): return {str(variant.group(1)).strip(): value} @staticmethod - def parse_string(line): + def parse_string(line, **kwargs): """Parses a text field Args: @@ -66,9 +106,10 @@ def parse_string(line): Returns: Parsed text field """ - return line.split(" ", 1)[1].strip(' "') + return line.split(None, 1)[1].strip(' "') + # return line.split(" ", 1)[1].strip(' "') - def parse_block_parameters(self, line): + def parse_block_parameters(self, line, **kwargs): """Parses a block parameters line Args: @@ -77,7 +118,7 @@ def parse_block_parameters(self, line): Returns: Parsed block parameters as list """ - parameters = line.split(" ", 1)[1] + parameters = line.split(None, 1)[1] parameters = " ".join(parameters.split()).split() return [self.convert_value(i) for i in parameters] @@ -103,6 +144,87 @@ def convert_value(value): except ValueError as err: raise ValueError(f"Cannot convert {value} from string to number.") from err + def parse_block_kennfeld(self, DcmThing, line_intro, dcm_file): + block_type, name, *shape = line_intro.split() + print(block_type, name) + dcm_thing = DcmThing(name=name, block_type=block_type) + + # Reverse the order of x and y to match numpy: + shape.reverse() + shape = tuple(map(int, shape)) + coord_y, coord_x = [], [] + + values: defaultdict[float, list] = defaultdict(list) + while True: + line = dcm_file.readline().strip() + + # Get the first keyword: + keyword = line.split(None, 1)[0] + + if keyword.startswith("END"): + + if block_type == "STUETZSTELLENVERTEILUNG": + values[None] = coord_x + dcm_thing.attrs["x_mapping"] = dcm_thing.name + + # Check if the parsing went ok: + if len(shape) > 0 and len(values) != shape[0]: + logger.error(f"Values dimension in {dcm_thing.name} does not match description!") + if len(shape) > 0 and len(coord_x) != shape[-1]: + logger.error(f"X dimension in {dcm_thing.name} do not match description!") + for name, entry in values.items(): + if len(shape) > 0 and len(entry) != shape[-1]: + logger.error(f"Values dimension in {dcm_thing.name} does not match description!") + + # Finalize the parsing: + dcm_thing.coords = tuple(v for _, v in enumerate((coord_y, coord_x)) if v) + dcm_thing.dims = tuple( + dcm_thing.attrs[k] for k in ("y_mapping", "x_mapping") if dcm_thing.attrs.get(k, None) + ) + + values_list = list(values.values()) + + values_fin: float | list[float] | list[list[float]] + if len(values_list) == 1 and len(dcm_thing.coords) == 0: + # single float + # squeeze out the wrapping list: + values_fin = values_list[0][0] + elif len(values) > 0 and len(dcm_thing.coords) == 1: + values_fin = values_list[0] + elif len(values) > 0 and len(dcm_thing.coords) > 1: + values_fin = values_list + else: + values_fin = None + dcm_thing.values = values_fin + + break + + else: + # Get parser settings for this keyword: + p = self.parser_methods.get(keyword, None) + + if p is not None: + # Parse the line: + parsed_values = p["method"]( + line, + # Used in functions: + coord_x=coord_x, + coord_y=coord_y, + values=values, + attrs=dcm_thing.attrs, + # Error handling: + name=dcm_thing.name, + ) + + # Optionally store in attrs, otherwise assume it's + # stored within the method: + if p["key"]: + dcm_thing.attrs[p["key"]] = parsed_values + else: + logger.warning(f"Unknown parameter field: {line}") + + return dcm_thing + def write(self, file) -> None: """Writes the current DCM object to a dcm file @@ -123,8 +245,6 @@ def read(self, file) -> None: """ _dcm_format = None - comment_qualifier = ("!", "*", ".") - with open(file, "r", encoding="utf-8") as dcm_file: for line in dcm_file: # Remove whitespaces @@ -169,37 +289,39 @@ def read(self, file) -> None: # Check if parameter starts elif line.startswith("FESTWERT "): - name = self.parse_string(line) - found_parameter = DcmParameter(name) - while True: - line = dcm_file.readline().strip() - - if line.startswith("END"): - break - - if line.startswith("LANGNAME"): - found_parameter.description = self.parse_string(line) - elif line.startswith("DISPLAYNAME"): - found_parameter.display_name = self.parse_string(line) - elif line.startswith("FUNKTION"): - found_parameter.function = self.parse_string(line) - elif line.startswith("WERT"): - found_parameter.value = self.convert_value(line.split(" ", 1)[1].strip()) - elif line.startswith("EINHEIT_W"): - found_parameter.unit = self.parse_string(line) - elif line.startswith("VAR"): - found_parameter.variants.update(self.parse_variant(line)) - elif line.startswith("TEXT"): - found_parameter.text = self.parse_string(line) - elif line.startswith(comment_qualifier): - if found_parameter.comment is None: - found_parameter.comment = line[1:].strip() + os.linesep - else: - found_parameter.comment += line[1:].strip() + os.linesep - else: - logger.warning("Unknown parameter field: %s", line) - - self._parameter_list.append(found_parameter) + self._parameter_list.append(self.parse_block_kennfeld(DcmParameter, line, dcm_file)) + + # name = self.parse_string(line) + # found_parameter = DcmParameter(name) + # while True: + # line = dcm_file.readline().strip() + + # if line.startswith("END"): + # break + + # if line.startswith("LANGNAME"): + # found_parameter.description = self.parse_string(line) + # elif line.startswith("DISPLAYNAME"): + # found_parameter.display_name = self.parse_string(line) + # elif line.startswith("FUNKTION"): + # found_parameter.function = self.parse_string(line) + # elif line.startswith("WERT"): + # found_parameter.value = self.convert_value(line.split(" ", 1)[1].strip()) + # elif line.startswith("EINHEIT_W"): + # found_parameter.unit = self.parse_string(line) + # elif line.startswith("VAR"): + # found_parameter.variants.update(self.parse_variant(line)) + # elif line.startswith("TEXT"): + # found_parameter.text = self.parse_string(line) + # elif line.startswith(comment_qualifier): + # if found_parameter.comment is None: + # found_parameter.comment = line[1:].strip() + os.linesep + # else: + # found_parameter.comment += line[1:].strip() + os.linesep + # else: + # logger.warning("Unknown parameter field: %s", line) + + # self._parameter_list.append(found_parameter) # Check if parameter block start elif line.startswith("FESTWERTEBLOCK"): @@ -253,16 +375,19 @@ def read(self, file) -> None: line = dcm_file.readline().strip() if line.startswith("END"): if len(stx) != found_characteristic_line.x_dimension: - logger.error("X dimension in %s \ - do not match description!", found_characteristic_line.name) + logger.error( + "X dimension in %s \ + do not match description!", + found_characteristic_line.name, + ) if len(parameters) != found_characteristic_line.x_dimension: logger.error( "Values dimension in %s \ - do not match description!", found_characteristic_line.name + do not match description!", + found_characteristic_line.name, ) found_characteristic_line.values = dict(zip(stx, parameters)) break - if line.startswith("LANGNAME"): found_characteristic_line.description = self.parse_string(line) elif line.startswith("DISPLAYNAME"): @@ -311,7 +436,8 @@ def read(self, file) -> None: if len(parameters) != found_fixed_characteristic_line.x_dimension: logger.error( "Values dimension in %s \ - do not match description!", found_fixed_characteristic_line.name + do not match description!", + found_fixed_characteristic_line.name, ) found_fixed_characteristic_line.values = dict(zip(stx, parameters)) break @@ -360,12 +486,14 @@ def read(self, file) -> None: if len(parameters) != found_group_characteristic_line.x_dimension: logger.error( "Values dimension in %s \ - do not match description!", found_group_characteristic_line.name + do not match description!", + found_group_characteristic_line.name, ) if len(stx) != found_group_characteristic_line.x_dimension: logger.error( "X dimension in %s \ - do not match description!", found_group_characteristic_line.name + do not match description!", + found_group_characteristic_line.name, ) found_group_characteristic_line.values = dict(zip(stx, parameters)) break @@ -402,262 +530,60 @@ def read(self, file) -> None: # Check for characteristic map elif line.startswith("KENNFELD "): - re_match = re.search(r"KENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip()) - found_characteristic_map = DcmCharacteristicMap(re_match.group(1)) - found_characteristic_map.x_dimension = self.convert_value(re_match.group(2)) - found_characteristic_map.y_dimension = self.convert_value(re_match.group(3)) - stx = [] - sty = None - - while True: - line = dcm_file.readline().strip() - if line.startswith("END"): - if len(found_characteristic_map.values) != found_characteristic_map.y_dimension: - logger.error( - "Values dimension in %s \ - does not match description!", found_characteristic_map.name - ) - if len(stx) != found_characteristic_map.x_dimension: - logger.error("X dimension in %s \ - do not match description!", found_characteristic_map.name) - for name, entry in found_characteristic_map.values.items(): - if len(entry) != found_characteristic_map.x_dimension: - logger.error( - "Values dimension in %s \ - does not match description!", found_characteristic_map.name - ) - else: - found_characteristic_map.values[name] = dict(zip(stx, entry)) - break - - if line.startswith("LANGNAME"): - found_characteristic_map.description = self.parse_string(line) - elif line.startswith("DISPLAYNAME"): - found_characteristic_map.display_name = self.parse_string(line) - elif line.startswith("FUNKTION"): - found_characteristic_map.function = self.parse_string(line) - elif line.startswith("WERT"): - if stx is None or sty is None: - raise ValueError(f"Values before stx/sty in {found_characteristic_map.name}") - parameters = self.parse_block_parameters(line) - if sty not in found_characteristic_map.values: - found_characteristic_map.values[sty] = [] - found_characteristic_map.values[sty].extend(parameters) - elif line.startswith("ST/X"): - stx.extend(self.parse_block_parameters(line)) - elif line.startswith("ST/Y"): - sty = self.convert_value(line.split(" ", 1)[1].strip()) - elif line.startswith("EINHEIT_W"): - found_characteristic_map.unit_values = self.parse_string(line) - elif line.startswith("EINHEIT_X"): - found_characteristic_map.unit_x = self.parse_string(line) - elif line.startswith("EINHEIT_Y"): - found_characteristic_map.unit_y = self.parse_string(line) - elif line.startswith("VAR"): - found_characteristic_map.variants.update(self.parse_variant(line)) - elif line.startswith(comment_qualifier): - re_match_x = re.search(r"SSTX\s+(.*)", line) - re_match_y = re.search(r"SSTY\s+(.*)", line) - if re_match_x: - found_characteristic_map.x_mapping = re_match_x.group(1) - elif re_match_y: - found_characteristic_map.y_mapping = re_match_y.group(1) - else: - if found_characteristic_map.comment is None: - found_characteristic_map.comment = line[1:].strip() + os.linesep - else: - found_characteristic_map.comment += line[1:].strip() + os.linesep - else: - logger.warning("Unknown parameter field: %s", line) - - self._characteristic_map_list.append(found_characteristic_map) + self._characteristic_map_list.append( + self.parse_block_kennfeld(DcmCharacteristicMap, line, dcm_file) + ) # Check for fixed characteristic map elif line.startswith("FESTKENNFELD "): - re_match = re.search(r"FESTKENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip()) - found_fixed_characteristic_map = DcmFixedCharacteristicMap(re_match.group(1)) - found_fixed_characteristic_map.x_dimension = self.convert_value(re_match.group(2)) - found_fixed_characteristic_map.y_dimension = self.convert_value(re_match.group(3)) - stx = [] - sty = None - - while True: - line = dcm_file.readline().strip() - if line.startswith("END"): - if len(found_fixed_characteristic_map.values) != found_fixed_characteristic_map.y_dimension: - logger.error( - "Values dimension in %s \ - does not match description!", found_fixed_characteristic_map.name - ) - if len(stx) != found_fixed_characteristic_map.x_dimension: - logger.error( - "X dimension in %s do not match description!", found_fixed_characteristic_map.name - ) - for name, entry in found_fixed_characteristic_map.values.items(): - if len(entry) != found_fixed_characteristic_map.x_dimension: - logger.error( - "Values dimension in %s \ - does not match description!", found_fixed_characteristic_map.name - ) - else: - found_fixed_characteristic_map.values[name] = dict(zip(stx, entry)) - break - - if line.startswith("LANGNAME"): - found_fixed_characteristic_map.description = self.parse_string(line) - elif line.startswith("DISPLAYNAME"): - found_fixed_characteristic_map.display_name = self.parse_string(line) - elif line.startswith("FUNKTION"): - found_fixed_characteristic_map.function = self.parse_string(line) - elif line.startswith("WERT"): - if stx is None or sty is None: - raise ValueError(f"Values before stx/sty in {found_fixed_characteristic_map.name}") - parameters = self.parse_block_parameters(line) - if sty not in found_fixed_characteristic_map.values: - found_fixed_characteristic_map.values[sty] = [] - found_fixed_characteristic_map.values[sty].extend(parameters) - elif line.startswith("ST/X"): - stx.extend(self.parse_block_parameters(line)) - elif line.startswith("ST/Y"): - sty = self.convert_value(line.split(" ", 1)[1].strip()) - elif line.startswith("EINHEIT_W"): - found_fixed_characteristic_map.unit_values = self.parse_string(line) - elif line.startswith("EINHEIT_X"): - found_fixed_characteristic_map.unit_x = self.parse_string(line) - elif line.startswith("EINHEIT_Y"): - found_fixed_characteristic_map.unit_y = self.parse_string(line) - elif line.startswith("VAR"): - found_fixed_characteristic_map.variants.update(self.parse_variant(line)) - elif line.startswith(comment_qualifier): - re_match_x = re.search(r"SSTX\s+(.*)", line) - re_match_y = re.search(r"SSTY\s+(.*)", line) - if re_match_x: - found_fixed_characteristic_map.x_mapping = re_match_x.group(1) - elif re_match_y: - found_fixed_characteristic_map.y_mapping = re_match_y.group(1) - else: - if found_fixed_characteristic_map.comment is None: - found_fixed_characteristic_map.comment = line[1:].strip() + os.linesep - else: - found_fixed_characteristic_map.comment += line[1:].strip() + os.linesep - else: - logger.warning("Unknown parameter field: %s", line) - - self._fixed_characteristic_map_list.append(found_fixed_characteristic_map) + self._fixed_characteristic_map_list.append( + self.parse_block_kennfeld(DcmFixedCharacteristicMap, line, dcm_file) + ) # Check for group characteristic map elif line.startswith("GRUPPENKENNFELD "): - re_match = re.search(r"GRUPPENKENNFELD\s+(.*?)\s+(\d+)\s+(\d+)", line.strip()) - - found_group_characteristic_map = DcmGroupCharacteristicMap(re_match.group(1)) - x_dimension = self.convert_value(re_match.group(2)) - y_dimension = self.convert_value(re_match.group(3)) - - stx = [] - sty = None - stys = [] - _values = dict() - - while True: - line = dcm_file.readline().strip() - if line.startswith("END"): - if len(_values) != y_dimension: - logger.error( - f"Values dimension in {found_group_characteristic_map.name} does not match description!" - ) - if len(stx) != x_dimension: - logger.error( - f"X dimension in {found_group_characteristic_map.name} do not match description!", - ) - for name, entry in _values.items(): - if len(entry) != x_dimension: - logger.error( - "Values dimension in {found_group_characteristic_map.name} does not match description!", - ) - - found_group_characteristic_map.values = list(_values.values()) - found_group_characteristic_map.coords = (stys, stx) - break - elif line.startswith("LANGNAME"): - found_group_characteristic_map.attrs["description"] = self.parse_string(line) - elif line.startswith("DISPLAYNAME"): - found_group_characteristic_map.attrs["display_name"] = self.parse_string(line) - elif line.startswith("FUNKTION"): - found_group_characteristic_map.attrs["function"] = self.parse_string(line) - elif line.startswith("WERT"): - if not (stx or stys): - raise ValueError(f"Values before stx/sty in {found_group_characteristic_map.name}") - parameters = self.parse_block_parameters(line) - if sty not in _values.keys(): - _values[sty] = [] - _values[sty].extend(parameters) - elif line.startswith("ST/X"): - stx.extend(self.parse_block_parameters(line)) - elif line.startswith("ST/Y"): - sty = self.convert_value(line.split(" ", 1)[1].strip()) - stys.append(sty) - elif line.startswith("EINHEIT_W"): - found_group_characteristic_map.attrs["units"] = self.parse_string(line) - elif line.startswith("EINHEIT_X"): - found_group_characteristic_map.attrs["units_x"] = self.parse_string(line) - elif line.startswith("EINHEIT_Y"): - found_group_characteristic_map.attrs["units_y"] = self.parse_string(line) - elif line.startswith("VAR"): - found_group_characteristic_map.attrs["variants"].update(self.parse_variant(line)) - elif line.startswith(comment_qualifier): - re_match_x = re.search(r"SSTX\s+(.*)", line) - re_match_y = re.search(r"SSTY\s+(.*)", line) - if re_match_x: - found_group_characteristic_map.attrs["x_mapping"] = re_match_x.group(1) - elif re_match_y: - found_group_characteristic_map.attrs["y_mapping"] = re_match_y.group(1) - else: - if found_group_characteristic_map.attrs["comment"] is None: - found_group_characteristic_map.attrs["comment"] = line[1:].strip() + os.linesep - else: - found_group_characteristic_map.attrs["comment"] += line[1:].strip() + os.linesep - else: - if not line.startswith("*"): - logger.warning(f"Unknown parameter field: {line}") - - self._group_characteristic_map_list.append(found_group_characteristic_map) + self._group_characteristic_map_list.append( + self.parse_block_kennfeld(DcmGroupCharacteristicMap, line, dcm_file) + ) # Check if distribution elif line.startswith("STUETZSTELLENVERTEILUNG"): - re_match = re.search(r"STUETZSTELLENVERTEILUNG\s+(.*?)\s+(\d+)", line.strip()) - found_distribution = DcmDistribution(re_match.group(1)) - found_distribution.x_dimension = self.convert_value(re_match.group(2)) - parameters = None - stx = None - - while True: - line = dcm_file.readline().strip() - if line.startswith("END"): - if len(found_distribution.values) != found_distribution.x_dimension: - logger.error("X dimension in %s do not match description!", found_distribution.name) - break - - if line.startswith("LANGNAME"): - found_distribution.description = self.parse_string(line) - elif line.startswith("DISPLAYNAME"): - found_distribution.display_name = self.parse_string(line) - elif line.startswith("FUNKTION"): - found_distribution.function = self.parse_string(line) - elif line.startswith("ST/X"): - found_distribution.values.extend(self.parse_block_parameters(line)) - elif line.startswith("EINHEIT_X"): - found_distribution.unit_x = self.parse_string(line) - elif line.startswith("VAR"): - found_distribution.variants.update(self.parse_variant(line)) - elif line.startswith(comment_qualifier): - if found_distribution.comment is None: - found_distribution.comment = line[1:].strip() + os.linesep - else: - found_distribution.comment += line[1:].strip() + os.linesep - else: - logger.warning("Unknown parameter field: %s", line) - - self._distribution_list.append(found_distribution) + self._distribution_list.append(self.parse_block_kennfeld(DcmDistribution, line, dcm_file)) + + # re_match = re.search(r"STUETZSTELLENVERTEILUNG\s+(.*?)\s+(\d+)", line.strip()) + # found_distribution = DcmDistribution(re_match.group(1)) + # found_distribution.x_dimension = self.convert_value(re_match.group(2)) + # parameters = None + # stx = None + + # while True: + # line = dcm_file.readline().strip() + # if line.startswith("END"): + # if len(found_distribution.values) != found_distribution.x_dimension: + # logger.error("X dimension in %s do not match description!", found_distribution.name) + # break + + # if line.startswith("LANGNAME"): + # found_distribution.description = self.parse_string(line) + # elif line.startswith("DISPLAYNAME"): + # found_distribution.display_name = self.parse_string(line) + # elif line.startswith("FUNKTION"): + # found_distribution.function = self.parse_string(line) + # elif line.startswith("ST/X"): + # found_distribution.values.extend(self.parse_block_parameters(line)) + # elif line.startswith("EINHEIT_X"): + # found_distribution.unit_x = self.parse_string(line) + # elif line.startswith("VAR"): + # found_distribution.variants.update(self.parse_variant(line)) + # elif line.startswith(comment_qualifier): + # if found_distribution.comment is None: + # found_distribution.comment = line[1:].strip() + os.linesep + # else: + # found_distribution.comment += line[1:].strip() + os.linesep + # else: + # logger.warning("Unknown parameter field: %s", line) + + # self._distribution_list.append(found_distribution) # Unknown start of line else: From b87c72e35f4991eeb1e542d568dc7c51e642b0cf Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 15 Jan 2023 22:46:29 +0100 Subject: [PATCH 14/28] Use dcm base class --- src/dcmReader/dcm_characteristic_map.py | 43 +++++-------- src/dcmReader/dcm_distribution.py | 60 ++++++------------ src/dcmReader/dcm_fixed_characteristic_map.py | 30 --------- src/dcmReader/dcm_group_characteristic_map.py | 30 --------- src/dcmReader/dcm_parameter.py | 62 ++++++------------- 5 files changed, 54 insertions(+), 171 deletions(-) diff --git a/src/dcmReader/dcm_characteristic_map.py b/src/dcmReader/dcm_characteristic_map.py index a48d769..b1a2ca2 100644 --- a/src/dcmReader/dcm_characteristic_map.py +++ b/src/dcmReader/dcm_characteristic_map.py @@ -3,9 +3,24 @@ """ from __future__ import annotations +from dataclasses import dataclass, field + from dcmReader.utils import _DcmBase +def _attrs_init() -> dict: + return { + "description": "", + "display_name": "", + "variants": {}, + "function": "", + "units_x": "", + "units_y": "", + "units": "", + } + + +@dataclass class DcmCharacteristicMap(_DcmBase): """Definition of a characteristic map @@ -26,30 +41,4 @@ class DcmCharacteristicMap(_DcmBase): y_dimension (int): Dimension in y direction of the characteristic maps """ - def __init__( - self, - name: str, - values: list[list[float]] | None = None, - coords: tuple[list[float], ...] = None, - attrs: dict | None = None, - *, - block_type: str = "KENNFELD", - ) -> None: - if attrs is None: - attrs = { - "description": "", - "display_name": "", - "variants": {}, - "function": "", - "units_x": "", - "units_y": "", - "units": "", - } - - super().__init__( - name=name, - values=values, - coords=coords, - attrs=attrs, - block_type=block_type, - ) + attrs: dict = field(default_factory=_attrs_init) diff --git a/src/dcmReader/dcm_distribution.py b/src/dcmReader/dcm_distribution.py index 6da8fde..ef3410e 100644 --- a/src/dcmReader/dcm_distribution.py +++ b/src/dcmReader/dcm_distribution.py @@ -1,11 +1,27 @@ """ Definition of DCM distribution """ -from dataclasses import dataclass +from __future__ import annotations + +from dataclasses import dataclass, field + +from dcmReader.utils import _DcmBase + + +def _attrs_init() -> dict: + return { + "description": "", + "display_name": "", + "variants": {}, + "function": "", + "units_x": "", + # "units": "", + "comment": "", + } @dataclass -class DcmDistribution: +class DcmDistribution(_DcmBase): """Definition of a distribution Attributes: @@ -20,42 +36,4 @@ class DcmDistribution: comment (str): Block comment """ - def __init__(self, name) -> None: - self.name = name - self.values = [] - self.description = None - self.display_name = None - self.variants = {} - self.function = None - self.unit_x = None - self.x_dimension = 0 - self.comment = None - - def __str__(self): - value = f"STUETZSTELLENVERTEILUNG {self.name} {str(self.x_dimension)}\n" - - if self.comment: - for line in self.comment.splitlines(True): - value += f"* {line}" - if self.description: - value += f' LANGNAME "{self.description}"\n' - if self.function: - value += f' FUNKTION "{self.function}"\n' - if self.display_name: - value += f" DISPLAYNAME {self.display_name}\n" - if self.unit_x: - value += f' EINHEIT_X "{self.unit_x}"\n' - if self.values: - x_entries = "" - for x_entry in self.values: - x_entries += f"{str(x_entry)} " - value += f' ST/X {x_entries.strip()}\n' - for var_name, var_value in self.variants.items(): - value += f" VAR {var_name}={var_value}\n" - - value += "END" - - return value - - def __lt__(self, other): - return self.function < other.function and self.description < other.description + attrs: dict = field(default_factory=_attrs_init) diff --git a/src/dcmReader/dcm_fixed_characteristic_map.py b/src/dcmReader/dcm_fixed_characteristic_map.py index 4666116..bb969e2 100644 --- a/src/dcmReader/dcm_fixed_characteristic_map.py +++ b/src/dcmReader/dcm_fixed_characteristic_map.py @@ -3,38 +3,8 @@ """ from __future__ import annotations -from dataclasses import dataclass from dcmReader.dcm_characteristic_map import DcmCharacteristicMap -@dataclass class DcmFixedCharacteristicMap(DcmCharacteristicMap): """Definition of a fixed characteristic map, derived from characteristic map""" - - def __init__( - self, - name: str, - values: list[list[float]] | None = None, - coords: tuple[list[float], ...] = None, - attrs: dict | None = None, - *, - block_type: str = "FESTKENNFELD", - ) -> None: - if attrs is None: - attrs = { - "description": "", - "display_name": "", - "variants": {}, - "function": "", - "units_x": "", - "units_y": "", - "units": "", - } - - super().__init__( - name=name, - values=values, - coords=coords, - attrs=attrs, - block_type=block_type, - ) diff --git a/src/dcmReader/dcm_group_characteristic_map.py b/src/dcmReader/dcm_group_characteristic_map.py index 12bab9c..e5734b9 100644 --- a/src/dcmReader/dcm_group_characteristic_map.py +++ b/src/dcmReader/dcm_group_characteristic_map.py @@ -3,38 +3,8 @@ """ from __future__ import annotations -from dataclasses import dataclass from dcmReader.dcm_characteristic_map import DcmCharacteristicMap -@dataclass class DcmGroupCharacteristicMap(DcmCharacteristicMap): """Definition of a group characteristic map, derived from characteristic map""" - - def __init__( - self, - name: str, - values: list[list[float]] | None = None, - coords: tuple[list[float], ...] = None, - attrs: dict | None = None, - *, - block_type: str = "GRUPPENKENNFELD", - ) -> None: - if attrs is None: - attrs = { - "description": "", - "display_name": "", - "variants": {}, - "function": "", - "units_x": "", - "units_y": "", - "units": "", - } - - super().__init__( - name=name, - values=values, - coords=coords, - attrs=attrs, - block_type=block_type, - ) diff --git a/src/dcmReader/dcm_parameter.py b/src/dcmReader/dcm_parameter.py index 96a417c..39dcd4b 100644 --- a/src/dcmReader/dcm_parameter.py +++ b/src/dcmReader/dcm_parameter.py @@ -1,9 +1,25 @@ """Definition of DCM parameter""" -from dataclasses import dataclass +from __future__ import annotations + +from dataclasses import dataclass, field + +from dcmReader.utils import _DcmBase + + +def _attrs_init() -> dict: + return { + "description": "", + "display_name": "", + "variants": {}, + "function": "", + "units_x": "", + "units_y": "", + "units": "", + } @dataclass -class DcmParameter: +class DcmParameter(_DcmBase): """Definition of a parameter Attributes: @@ -18,44 +34,4 @@ class DcmParameter: comment (str): Block comment """ - def __init__(self, name): - self.name = name - self.value = None - self.description = None - self.display_name = None - self.variants = {} - self.function = None - self.unit = None - self.text = None - self.comment = None - - def __str__(self): - value = f"FESTWERT {self.name}\n" - - if self.comment: - for line in self.comment.splitlines(True): - value += f"* {line}" - if self.description: - value += f' LANGNAME "{self.description}"\n' - if self.function: - value += f' FUNKTION "{self.function}"\n' - if self.display_name: - value += f" DISPLAYNAME {self.display_name}\n" - if self.unit: - value += f' EINHEIT_W "{self.unit}"\n' - if self.value: - value += f" WERT {self.value}\n" - if self.text: - value += f' TEXT "{self.text}"\n' - - for var_name, var_value in self.variants.items(): - if self.value: - value += f" VAR {var_name}={var_value}\n" - else: - value += f' VAR {var_name}="{var_value}"\n' - value += "END" - - return value - - def __lt__(self, other): - return self.function < other.function and self.description < other.description + attrs: dict = field(default_factory=_attrs_init) From 67af697c8939e304cd41411e3db0a67ca6bc371b Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sat, 21 Jan 2023 19:13:55 +0100 Subject: [PATCH 15/28] Ok I give up on doing this without numpy FESTWERTEBLOCK blockParameter2D 4 @ 2 was the last straw. How are you supposed to keep track of dimensions without the st/y and handle if they're split because linelength limits? Going with numpy will deal with all the annoying error handling and once the values are all parsed in a big 1d array then do a simple reshape. --- src/dcmReader/dcm_parameter_block.py | 143 ++++++++++++-------- src/dcmReader/dcm_reader.py | 195 ++++++++++++++++----------- src/dcmReader/utils.py | 154 ++++++++++++--------- tests/Sample.dcm | 57 ++++---- 4 files changed, 331 insertions(+), 218 deletions(-) diff --git a/src/dcmReader/dcm_parameter_block.py b/src/dcmReader/dcm_parameter_block.py index 2598215..fd031bb 100644 --- a/src/dcmReader/dcm_parameter_block.py +++ b/src/dcmReader/dcm_parameter_block.py @@ -1,66 +1,101 @@ """ Definition of DCM parameter block """ -from dataclasses import dataclass +from __future__ import annotations + +from dataclasses import dataclass, field + +from dcmReader.utils import _DcmBase + + +def _attrs_init() -> dict: + return { + "description": "", + "display_name": "", + "variants": {}, + "function": "", + "units_x": "", + "units_y": "", + "units": "", + } @dataclass -class DcmParameterBlock: - """Definition of a block parameter +class DcmParameterBlock(_DcmBase): + """Definition of a parameter Attributes: - name (str): Name of the block parameter - description (str): Description of the block parameter, started by LANGNAME in DCM - display_name (str): Block parameter name according asam-2mc, started by DISPLAYNAME in DCM - variants (dict): Variants for the block parameter, started by VAR in DCM + name (str): Name of the parameter + description (str): Description of the parameter, started by LANGNAME in DCM + display_name (str): Parameter name according asam-2mc, started by DISPLAYNAME in DCM + variants (dict): Variants for the parameter, started by VAR in DCM function (str): Name of the assigned function, started by FUNKTION in DCM - unit (str): Unit of the block parameters, started by EINHEIT_W in DCM - values (list): List of values of the block parameter, started by WERT in DCM - x_dimension (int): Dimension in x direction of the block parameter - y_dimension (int): Dimension in y direction of the block parameter + unit (str): Unit of the parameter, started by EINHEIT_W in DCM + value (float/int): Value of the parameter, started by WERT in DCM + text (str): Alternative text-value, started by TEXT in DCM comment (str): Block comment """ - def __init__(self, name): - self.name = name - self.values = [] - self.description = None - self.display_name = None - self.variants = {} - self.function = None - self.unit = None - self.x_dimension = 0 - self.y_dimension = 0 - self.comment = None - - def __str__(self): - value = f"FESTWERTEBLOCK {self.name} {self.x_dimension}" - if self.y_dimension > 1: - value += f" @ {self.y_dimension}\n" - else: - value += "\n" - - if self.comment: - for line in self.comment.splitlines(True): - value += f"* {line}" - if self.description: - value += f' LANGNAME "{self.description}"\n' - if self.function: - value += f' FUNKTION "{self.function}"\n' - if self.display_name: - value += f" DISPLAYNAME {self.display_name}\n" - if self.unit: - value += f' EINHEIT_W "{self.unit}"\n' - if self.values: - for entry in self.values: - value += f' WERT {" ".join([str(x) for x in entry])}\n' - - for var_name, var_value in self.variants.items(): - value += f" VAR {var_name}={var_value}\n" - - value += "END" - - return value - - def __lt__(self, other): - return self.function < other.function and self.description < other.description + attrs: dict = field(default_factory=_attrs_init) + + +# @dataclass +# class DcmParameterBlock: +# """Definition of a block parameter + +# Attributes: +# name (str): Name of the block parameter +# description (str): Description of the block parameter, started by LANGNAME in DCM +# display_name (str): Block parameter name according asam-2mc, started by DISPLAYNAME in DCM +# variants (dict): Variants for the block parameter, started by VAR in DCM +# function (str): Name of the assigned function, started by FUNKTION in DCM +# unit (str): Unit of the block parameters, started by EINHEIT_W in DCM +# values (list): List of values of the block parameter, started by WERT in DCM +# x_dimension (int): Dimension in x direction of the block parameter +# y_dimension (int): Dimension in y direction of the block parameter +# comment (str): Block comment +# """ + +# def __init__(self, name): +# self.name = name +# self.values = [] +# self.description = None +# self.display_name = None +# self.variants = {} +# self.function = None +# self.unit = None +# self.x_dimension = 0 +# self.y_dimension = 0 +# self.comment = None + +# def __str__(self): +# value = f"FESTWERTEBLOCK {self.name} {self.x_dimension}" +# if self.y_dimension > 1: +# value += f" @ {self.y_dimension}\n" +# else: +# value += "\n" + +# if self.comment: +# for line in self.comment.splitlines(True): +# value += f"* {line}" +# if self.description: +# value += f' LANGNAME "{self.description}"\n' +# if self.function: +# value += f' FUNKTION "{self.function}"\n' +# if self.display_name: +# value += f" DISPLAYNAME {self.display_name}\n" +# if self.unit: +# value += f' EINHEIT_W "{self.unit}"\n' +# if self.values: +# for entry in self.values: +# value += f' WERT {" ".join([str(x) for x in entry])}\n' + +# for var_name, var_value in self.variants.items(): +# value += f" VAR {var_name}={var_value}\n" + +# value += "END" + +# return value + +# def __lt__(self, other): +# return self.function < other.function and self.description < other.description diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index b86db7d..13247c1 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -1,12 +1,14 @@ """ DCMReader which handles data parsing. """ +from __future__ import annotations import os import re import logging from collections import defaultdict +from typing import TYPE_CHECKING from dcmReader.dcm_parameter import DcmParameter from dcmReader.dcm_function import DcmFunction @@ -18,6 +20,10 @@ from dcmReader.dcm_fixed_characteristic_map import DcmFixedCharacteristicMap from dcmReader.dcm_group_characteristic_map import DcmGroupCharacteristicMap from dcmReader.dcm_distribution import DcmDistribution +from dcmReader.utils import _get_shape + +if TYPE_CHECKING: + from dcmReader.utils import ArrayLike logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG) logger = logging.getLogger(__name__) @@ -46,37 +52,59 @@ def __init__(self): "LANGNAME": {"key": "description", "method": self.parse_string}, "DISPLAYNAME": {"key": "display_name", "method": self.parse_string}, "FUNKTION": {"key": "function", "method": self.parse_string}, - "WERT": {"key": "", "method": self.parse_wert}, - "ST/X": {"key": "", "method": self.parse_coord_x}, - "ST/Y": {"key": "", "method": self.parse_coord_y}, + "WERT": {"key": "", "method": self._parse_wert}, + "ST/X": {"key": "", "method": self._parse_coord_x}, + "ST/Y": {"key": "", "method": self._parse_coord_y}, "EINHEIT_W": {"key": "units", "method": self.parse_string}, "EINHEIT_X": {"key": "units_x", "method": self.parse_string}, "EINHEIT_Y": {"key": "units_y", "method": self.parse_string}, "VAR": {"key": "variants", "method": self.parse_string}, - "TEXT": {"key": "text", "method": self.parse_string}, - "*SSTX": {"key": "x_mapping", "method": self.parse_string}, - "*SSTY": {"key": "y_mapping", "method": self.parse_string}, + "TEXT": {"key": "", "method": self._parse_text}, + # Data from comments: + "SSTX": {"key": "x_mapping", "method": self.parse_string}, + "SSTY": {"key": "y_mapping", "method": self.parse_string}, } - self.parser_methods.update({k: {"key": "", "method": self.parse_comment} for k in comment_qualifier}) + self.parser_methods.update({k: {"key": "", "method": self._parse_comment} for k in comment_qualifier}) - def parse_wert(self, line: str, *, coord_x, coord_y, values: defaultdict, **kwargs): + def _parse_wert(self, line: str, *, coord_x, coord_y, values: defaultdict, **kwargs): # if not (coord_x or coord_y): # raise ValueError(f"Values before stx/sty in {kwargs.get('name', 'Error')}") sty = coord_y[-1] if len(coord_y) > 0 else None values[sty].extend(self.parse_block_parameters(line)) - def parse_coord_x(self, line: str, *, coord_x: list, **kwargs) -> None: + def _parse_text(self, line: str, *, coord_x, coord_y, values: defaultdict, **kwargs): + sty = coord_y[-1] if len(coord_y) > 0 else None + + parameters = line.split(None, 1)[1] + # parameters = " ".join(parameters.split()).split() + parameters = [s.strip("\"'") for s in parameters.split()] + values[sty].extend(parameters) + + def _parse_coord_x(self, line: str, *, coord_x: list, **kwargs) -> None: coord_x.extend(self.parse_block_parameters(line)) - def parse_coord_y(self, line: str, *, coord_y: list, **kwargs) -> None: - self.parse_coord_x(line, coord_x=coord_y) + def _parse_coord_y(self, line: str, *, coord_y: list, **kwargs) -> None: + self._parse_coord_x(line, coord_x=coord_y) + + def _parse_comment(self, line: str, *, attrs: dict, **kwargs): + # TODO: Should the comment remember which row? So it can be reprinted there? - def parse_var(self, line: str, *, attrs: dict, key: str, **kwargs) -> None: - attrs[key].update(self.parse_variant(line)) + cq, line_no_cq = line[0], line[1:].strip() + k = line_no_cq.split(None, 1)[0] - def parse_comment(self, line: str, *, attrs: dict, **kwargs): - attrs["comment"] = attrs.get("comment", "") + line[1:].strip() + os.linesep + p = self.parser_methods.get(k, None) + if p is not None: + # The comment has known keyword, parse it accordingly: + parsed_values = p["method"](line_no_cq, attrs=attrs, **kwargs) + + # Optionally store in attrs, otherwise assume it's + # stored within the method: + if p["key"]: + attrs[p["key"]] = parsed_values + else: + cmnt_prev = attrs.get("comment", "") + attrs["comment"] = f"{cmnt_prev}{line_no_cq}{os.linesep}" def parse_variant(self, line, **kwargs): """Parses a variant field @@ -145,13 +173,13 @@ def convert_value(value): raise ValueError(f"Cannot convert {value} from string to number.") from err def parse_block_kennfeld(self, DcmThing, line_intro, dcm_file): - block_type, name, *shape = line_intro.split() - print(block_type, name) + block_type, name, *shape_rev = line_intro.split() + # print(block_type, name) dcm_thing = DcmThing(name=name, block_type=block_type) # Reverse the order of x and y to match numpy: - shape.reverse() - shape = tuple(map(int, shape)) + shape_rev.reverse() + shape: tuple[int, ...] = tuple(int(v) for v in shape_rev if v != "@") coord_y, coord_x = [], [] values: defaultdict[float, list] = defaultdict(list) @@ -167,42 +195,55 @@ def parse_block_kennfeld(self, DcmThing, line_intro, dcm_file): values[None] = coord_x dcm_thing.attrs["x_mapping"] = dcm_thing.name - # Check if the parsing went ok: - if len(shape) > 0 and len(values) != shape[0]: - logger.error(f"Values dimension in {dcm_thing.name} does not match description!") - if len(shape) > 0 and len(coord_x) != shape[-1]: - logger.error(f"X dimension in {dcm_thing.name} do not match description!") - for name, entry in values.items(): - if len(shape) > 0 and len(entry) != shape[-1]: - logger.error(f"Values dimension in {dcm_thing.name} does not match description!") - - # Finalize the parsing: - dcm_thing.coords = tuple(v for _, v in enumerate((coord_y, coord_x)) if v) - dcm_thing.dims = tuple( - dcm_thing.attrs[k] for k in ("y_mapping", "x_mapping") if dcm_thing.attrs.get(k, None) - ) - + # Handle coords and dims: + xy = ("x", "y") + k_axis = tuple(f"{v}_mapping" for v in xy) + crds_rev = (coord_x, coord_y) + dims_rev = [] + coords_rev = [] + for i, v in enumerate(reversed(shape)): + dims_rev.append(dcm_thing.attrs.get(k_axis[i], f"{dcm_thing.name}_{xy[i]}")) + if crds_rev[i]: + coords_rev.append(crds_rev[i]) + dcm_thing.dims = tuple(reversed(dims_rev)) + dcm_thing.coords = tuple(reversed(coords_rev)) + + # Handle values: values_list = list(values.values()) - - values_fin: float | list[float] | list[list[float]] - if len(values_list) == 1 and len(dcm_thing.coords) == 0: - # single float + values_fin: ArrayLike + if len(values_list) == 1 and len(shape) == 0: + # constant single float # squeeze out the wrapping list: values_fin = values_list[0][0] - elif len(values) > 0 and len(dcm_thing.coords) == 1: + elif len(values) > 0 and len(shape) == 1: + # Tables: values_fin = values_list[0] - elif len(values) > 0 and len(dcm_thing.coords) > 1: + elif len(values) > 0 and len(shape) > 1: + # Maps: values_fin = values_list else: + # Error? values_fin = None dcm_thing.values = values_fin + # Check if the parsing went ok: + value_shape = _get_shape(dcm_thing.values) + if value_shape != shape: + logger.error( + f"The parsed shape, {value_shape}, does not match the " + f"expected shape, {shape}, from the '{block_type} {name}'-block" + ) + break else: # Get parser settings for this keyword: p = self.parser_methods.get(keyword, None) + if p is None: + # Is it a comment? + p = self.parser_methods.get(keyword[0], None) + if p is not None: # Parse the line: parsed_values = p["method"]( @@ -221,7 +262,7 @@ def parse_block_kennfeld(self, DcmThing, line_intro, dcm_file): if p["key"]: dcm_thing.attrs[p["key"]] = parsed_values else: - logger.warning(f"Unknown parameter field: {line}") + logger.warning(f"JW: Unknown parameter field: {line=}{keyword=}") return dcm_thing @@ -325,43 +366,45 @@ def read(self, file) -> None: # Check if parameter block start elif line.startswith("FESTWERTEBLOCK"): - block_data = re.search(r"FESTWERTEBLOCK\s+(.*?)\s+(\d+)(?:\s+\@\s+(\d+))?", line.strip()) - found_block_parameter = DcmParameterBlock(block_data.group(1)) - found_block_parameter.x_dimension = self.convert_value(block_data.group(2)) - found_block_parameter.y_dimension = ( - self.convert_value(block_data.group(3)) if block_data.group(3) is not None else 1 - ) - while True: - line = dcm_file.readline().strip() - if line.startswith("END"): - if len(found_block_parameter.values) != found_block_parameter.y_dimension: - logger.error("Y dimension in %s do not match description!", found_block_parameter.name) - break + self._block_parameter_list.append(self.parse_block_kennfeld(DcmParameter, line, dcm_file)) + + # block_data = re.search(r"FESTWERTEBLOCK\s+(.*?)\s+(\d+)(?:\s+\@\s+(\d+))?", line.strip()) + # found_block_parameter = DcmParameterBlock(block_data.group(1)) + # found_block_parameter.x_dimension = self.convert_value(block_data.group(2)) + # found_block_parameter.y_dimension = ( + # self.convert_value(block_data.group(3)) if block_data.group(3) is not None else 1 + # ) + # while True: + # line = dcm_file.readline().strip() + # if line.startswith("END"): + # if len(found_block_parameter.values) != found_block_parameter.y_dimension: + # logger.error("Y dimension in %s do not match description!", found_block_parameter.name) + # break - if line.startswith("LANGNAME"): - found_block_parameter.description = self.parse_string(line) - elif line.startswith("DISPLAYNAME"): - found_block_parameter.display_name = self.parse_string(line) - elif line.startswith("FUNKTION"): - found_block_parameter.function = self.parse_string(line) - elif line.startswith("WERT"): - parameters = self.parse_block_parameters(line) - if len(parameters) != found_block_parameter.x_dimension: - logger.error("X dimension in %s do not match description!", found_block_parameter.name) - found_block_parameter.values.append(parameters) - elif line.startswith("EINHEIT_W"): - found_block_parameter.unit = self.parse_string(line) - elif line.startswith("VAR"): - found_block_parameter.variants.update(self.parse_variant(line)) - elif line.startswith(comment_qualifier): - if found_block_parameter.comment is None: - found_block_parameter.comment = line[1:].strip() + os.linesep - else: - found_block_parameter.comment += line[1:].strip() + os.linesep - else: - logger.warning("Unknown parameter field: %s", line) + # if line.startswith("LANGNAME"): + # found_block_parameter.description = self.parse_string(line) + # elif line.startswith("DISPLAYNAME"): + # found_block_parameter.display_name = self.parse_string(line) + # elif line.startswith("FUNKTION"): + # found_block_parameter.function = self.parse_string(line) + # elif line.startswith("WERT"): + # parameters = self.parse_block_parameters(line) + # if len(parameters) != found_block_parameter.x_dimension: + # logger.error("X dimension in %s do not match description!", found_block_parameter.name) + # found_block_parameter.values.append(parameters) + # elif line.startswith("EINHEIT_W"): + # found_block_parameter.unit = self.parse_string(line) + # elif line.startswith("VAR"): + # found_block_parameter.variants.update(self.parse_variant(line)) + # elif line.startswith(comment_qualifier): + # if found_block_parameter.comment is None: + # found_block_parameter.comment = line[1:].strip() + os.linesep + # else: + # found_block_parameter.comment += line[1:].strip() + os.linesep + # else: + # logger.warning("Unknown parameter field: %s", line) - self._block_parameter_list.append(found_block_parameter) + # self._block_parameter_list.append(found_block_parameter) # Check if characteristic line elif line.startswith("KENNLINIE"): diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py index bf3351f..d9839d9 100644 --- a/src/dcmReader/utils.py +++ b/src/dcmReader/utils.py @@ -4,23 +4,21 @@ from __future__ import annotations import math +import os from typing import TYPE_CHECKING, Protocol from abc import ABC, abstractmethod from dataclasses import dataclass, field if TYPE_CHECKING: - try: - from numpy.typing import ArrayLike - except ImportError: - from typing import Union + from typing import Union - ArrayLike = Union[float, list[float], list[list[float]]] + ArrayLike = float | str | list[float | str] | list[list[float | str]] -def _get_shape(ndarray: list | float) -> tuple[int, ...]: +def _get_shape(array_like: ArrayLike, *, check_if_ragged: bool = False) -> tuple[int, ...]: """ - Get the shape of array-like. + Get the shape of array_like. Examples -------- @@ -34,15 +32,36 @@ def _get_shape(ndarray: list | float) -> tuple[int, ...]: (1, 1) >>> _get_shape([[1], [2]]) (2, 1) + + Calculating the shape from a ragged sequence does not work. It's possible to check + for these inconsistencies although at a cost of performance: + + >>> try: + >>> _get_shape([[0, 1, 2], [4, 5]], check_if_ragged=True) + >>> except ValueError as e: + >>> msg = str(e) + >>> print(f"{msg[:48]} (...) {msg[-17:]}") + >>> _get_shape([[0, 1, 2], [4, 5]], check_if_ragged=True) + Calculating a shape from ragged nested sequences (...) is not supported. """ # https://stackoverflow.com/questions/51960857/how-can-i-get-a-list-shape-without-using-numpy - if isinstance(ndarray, list): + if isinstance(array_like, list): # More dimensions, so make a recursive call - outermost_size = len(ndarray) + outermost_size = len(array_like) if outermost_size == 0: return (outermost_size,) else: - return (outermost_size, *_get_shape(ndarray[0])) + if check_if_ragged: + # This costs some + it = iter(array_like) + the_len = len(next(it)) + if not all(len(l) == the_len for l in it): + raise ValueError( + "Calculating a shape from ragged nested sequences " + "(which is a list-or-tuple of lists-or-tuples " + "with different lengths or shapes) is not supported." + ) + return (outermost_size, *_get_shape(array_like[0])) else: # No more dimensions, so we're done return () @@ -93,6 +112,27 @@ def __len__(self) -> int: raise TypeError("len() of unsized object") +def _to_str(val: str | int | float | list, delimiter: str = " ") -> str: + val_list: list = [val] if isinstance(val, (str, int, float)) else val + return delimiter.join(map(str, val_list)) + + +def _print_values(key: str, value: str | int | float | list, n: int = 6, pad: int = 14) -> str: + """Print pairwise values prettily.""" + # Normalize so that value is always a list: + value_list: list = [value] if isinstance(value, (str, int, float)) else value + + # Split too long lists into chunks. + value_list_chunked = [value_list[i : i + n] for i in range(0, len(value_list), n)] + + # Return the key and value in a table-format: + pad_ = f"<{pad}" + out = "" + for v in value_list_chunked: + out += f" {key:{pad_}}{_to_str(v)}\n" + return out + + def _attrs_init() -> dict: return { "description": "", @@ -116,85 +156,71 @@ class _DcmBase(ShapeRelatedMixin): block_type: str = "" def __lt__(self, other): - return ( - self.attrs["function"] < other.attrs["function"] and self.attrs["description"] < other.attrs["description"] - ) + self_function = self.attrs.get("function", None) + self_description = self.attrs.get("descrition", None) + other_function = other.attrs.get("function", None) + other_description = other.attrs.get("descrition", None) + return self_function < other_function and self_description < other_description - def _print_dcm_format(self, name: str, is_function: False) -> str: + def _print_dcm_format(self) -> str: """ Print the data according to the dcm-format. - - Arrays longer than 6 are split to new line. - - Parameters - ---------- - name : str - Name of the block. - is_function : False - Is the block a 'FUNKTIONEN'. """ - def to_str(val: str | int | float | list, delimiter: str = " ") -> str: - val_list: list = [val] if isinstance(val, (str, int, float)) else val - return delimiter.join(map(str, val_list)) - - def print_values(key: str, value: str | int | float | list, n: int = 6) -> str: - """Print pairwise values prettily.""" - # Normalize so that val is always a list: - value_list: list = [value] if isinstance(value, (str, int, float)) else value - - # Split too long lists into chunks. - value_list_chunked = [value_list[i : i + n] for i in range(0, len(value_list), n)] + if self.block_type == "FUNKTIONEN": + return f'{self.block_type} {self.name} "{self.version}" "{self.description}"' - out = "" - for v in value_list_chunked: - out += f" {key: <13} {to_str(v)}\n" - return out - - if is_function: - return f'{name} {self.name} "{self.version}" "{self.description}"' - - shape_rev = list(reversed(self.shape)) - coords_rev = list(reversed(self.coords)) ndim = self.ndim + shape_rev: list[int | str] = list(reversed(self.shape)) + if self.block_type == "FESTWERTEBLOCK" and ndim == 2: + shape_rev.insert(1, "@") + coords_rev = list(reversed(self.coords)) - value = f"{name} {self.name} {to_str(shape_rev)}\n" + # Header: + value = f"{self.block_type} {self.name} {_to_str(shape_rev)}\n" + # Attributes printed before the values: ks = ( - ("LANGNAME", "description", lambda x: f'"{x}"'), - ("FUNKTION", "function", lambda x: f"{x}"), - ("DISPLAYNAME", "display_name", lambda x: f'"{x}"'), - ("EINHEIT_X", "units_x", lambda x: f'"{x}"'), - ("EINHEIT_Y", "units_y", lambda x: f'"{x}"'), - ("EINHEIT_W", "units", lambda x: f'"{x}"'), - ("*SSTX", "x_mapping", lambda x: f"{x}"), - ("*SSTY", "y_mapping", lambda x: f"{x}"), + # TODO: Should comments be reprinted? They have currently lost the position + # so the comment might be out of context. + # ("*", "comment", lambda x: f"{', '.join(x.split(os.linesep))}", {"pad": 0, "n": 1}), + ("LANGNAME", "description", lambda x: f'"{x}"', {}), + ("FUNKTION", "function", lambda x: f"{x}", {}), + ("DISPLAYNAME", "display_name", lambda x: f'"{x}"', {}), + ("EINHEIT_X", "units_x", lambda x: f'"{x}"', {}), + ("EINHEIT_Y", "units_y", lambda x: f'"{x}"', {}), + ("EINHEIT_W", "units", lambda x: f'"{x}"', {}), + ("*SSTX", "x_mapping", lambda x: f"{x}", {}), + ("*SSTY", "y_mapping", lambda x: f"{x}", {}), # Printed after WERT: - ("TEXT", "text", lambda x: f"{x}"), - ("VAR", "variants", lambda x: f"{x}"), + ("TEXT", "text", lambda x: f"{x}", {}), # Is this equivalent to WERT? + ("VAR", "variants", lambda x: f"{x}", {}), ) idx_as_suffx = 2 - for k, v, f in ks[:-idx_as_suffx]: + for k, v, f, kws in ks[:-idx_as_suffx]: if self.attrs.get(v, ""): - value += print_values(k, f"{f(self.attrs[v])}") + value += _print_values(k, f"{f(self.attrs[v])}", **kws) + # x-values: if ndim > 0: - value += print_values("ST/X", coords_rev[0]) + value += _print_values("ST/X", coords_rev[0]) + # y-value and values: for i, val in enumerate(self.values if ndim > 1 else [self.values]): if ndim > 1: - value += print_values("ST/Y", coords_rev[1][i]) + value += _print_values("ST/Y", coords_rev[1][i]) - value += print_values("WERT", val) + value += _print_values("WERT", val) - for k, v, f in ks[-idx_as_suffx:]: + # Attributes printed after the values: + for k, v, f, kws in ks[-idx_as_suffx:]: if self.attrs.get(v, ""): - value += print_values(k, f"{f(self.attrs[v])}") + value += _print_values(k, f"{f(self.attrs[v])}", **kws) + # Close: value += "END" return value def __str__(self) -> str: - is_function = self.block_type == "FUNKTIONEN" - return self._print_dcm_format(self.block_type, is_function) + return self._print_dcm_format() diff --git a/tests/Sample.dcm b/tests/Sample.dcm index bda37c0..5dfaf63 100644 --- a/tests/Sample.dcm +++ b/tests/Sample.dcm @@ -36,6 +36,15 @@ FESTWERT textParameter VAR VariantA="ParameterB" END +FESTWERTEBLOCK blockTextParameter1D 2 +* Sample comment + LANGNAME "Sample block parameters" + FUNKTION "BlockParameterFunction" + DISPLAYNAME BlockParameterDisplayname + EINHEIT_W "°C" + TEXT "ParameterA" "ParameterB" +END + FESTWERTEBLOCK blockParameter1D 4 * Sample comment LANGNAME "Sample block parameters" @@ -61,7 +70,7 @@ KENNLINIE characteristicLine 8 DISPLAYNAME CharacteristicLineDisplayname EINHEIT_X "s" EINHEIT_W "°" -*SSTX DISTRIBUTION X +*SSTX DISTRIBUTION X8 ST/X 0.0 1.0 2.0 3.0 ST/X 4.0 5.0 6.0 7.0 WERT 0.0 80.0 120.0 180.0 @@ -75,9 +84,9 @@ FESTKENNLINIE fixedCharacteristicLine 6 DISPLAYNAME FixedCharacteristicLineDisplayname EINHEIT_X "s" EINHEIT_W "°" - *SSTX DISTRIBUTION X + *SSTX DISTRIBUTION X6 ST/X 0.0 1.0 2.0 - ST/X 3.0 4.0 5.0 + ST/X 3.0 4.0 5.0 WERT 45.0 90.0 135.0 WERT 180.0 225.0 270.0 END @@ -89,7 +98,7 @@ GRUPPENKENNLINIE groupCharacteristicLine 3 DISPLAYNAME GroupCharacteristicLineDisplayname EINHEIT_X "s" EINHEIT_W "°" -* SSTX DISTRIBUTION X +* SSTX DISTRIBUTION X3 ST/X 1.0 2.0 3.0 WERT -45.0 -90.0 -135.0 END @@ -102,15 +111,15 @@ KENNFELD characteristicMap 6 2 EINHEIT_X "°C" EINHEIT_Y "m/s" EINHEIT_W "bar" -*SSTX DISTRIBUTION X -*SSTY DISTRIBUTION Y +*SSTX DISTRIBUTION X6 +*SSTY DISTRIBUTION Y2 ST/X 1.0 2.0 3.0 4.0 5.0 6.0 - ST/Y 1.0 + ST/Y 1.0 WERT 0.0 0.4 0.8 1.0 1.4 1.8 - ST/Y 2.0 + ST/Y 2.0 WERT 1.0 2.0 3.0 2.0 3.0 4.0 END - + FESTKENNFELD fixedCharacteristicMap 6 2 * Sample comment LANGNAME "Sample fixed characteristic map" @@ -119,14 +128,14 @@ FESTKENNFELD fixedCharacteristicMap 6 2 EINHEIT_X "°C" EINHEIT_Y "m/s" EINHEIT_W "bar" - *SSTX DISTRIBUTION X - *SSTY DISTRIBUTION Y + *SSTX DISTRIBUTION X6 + *SSTY DISTRIBUTION Y2 ST/X 1.0 2.0 3.0 ST/X 4.0 5.0 6.0 - ST/Y 0.0 + ST/Y 0.0 WERT 0.0 0.4 0.8 WERT 1.0 1.4 1.8 - ST/Y 1.0 + ST/Y 1.0 WERT 1.0 2.0 3.0 2.0 3.0 4.0 END @@ -138,22 +147,22 @@ GRUPPENKENNFELD groupCharacteristicMap 6 3 EINHEIT_X "°C" EINHEIT_Y "m/s" EINHEIT_W "bar" -* SSTX DISTRIBUTION X -* SSTY DISTRIBUTION Y - ST/X 1.0 2.0 3.0 +* SSTX DISTRIBUTION X6 +* SSTY DISTRIBUTION Y3 + ST/X 1.0 2.0 3.0 ST/X 4.0 5.0 6.0 - ST/Y 1.0 - WERT 1.0 2.0 3.0 - WERT 2.0 3.0 4.0 - ST/Y 2.0 - WERT 2.0 4.0 6.0 - WERT 3.0 4.0 5.0 - ST/Y 3.0 + ST/Y 1.0 + WERT 1.0 2.0 3.0 + WERT 2.0 3.0 4.0 + ST/Y 2.0 + WERT 2.0 4.0 6.0 + WERT 3.0 4.0 5.0 + ST/Y 3.0 WERT 3.0 6.0 9.0 WERT 7.0 8.0 9.0 END -STUETZSTELLENVERTEILUNG distrib 3 +STUETZSTELLENVERTEILUNG distrib 3 *SST LANGNAME "Sample distribution" FUNKTION "DistributionFunction" From c84d3924c16cb6eab16f328cbfa9ab8440f50149 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 22 Jan 2023 12:52:44 +0100 Subject: [PATCH 16/28] Create py.typed --- src/dcmReader/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/dcmReader/py.typed diff --git a/src/dcmReader/py.typed b/src/dcmReader/py.typed new file mode 100644 index 0000000..e69de29 From 27314450271dad5149dbb23e2755a17aa99475c3 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Fri, 27 Jan 2023 23:16:44 +0100 Subject: [PATCH 17/28] remove duplicated parse code --- src/dcmReader/dcm_reader.py | 594 ++++++++++-------------------------- 1 file changed, 160 insertions(+), 434 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 13247c1..1180635 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -8,22 +8,33 @@ import logging from collections import defaultdict -from typing import TYPE_CHECKING - -from dcmReader.dcm_parameter import DcmParameter -from dcmReader.dcm_function import DcmFunction -from dcmReader.dcm_parameter_block import DcmParameterBlock -from dcmReader.dcm_characteristic_line import DcmCharacteristicLine -from dcmReader.dcm_fixed_characteristic_line import DcmFixedCharacteristicLine -from dcmReader.dcm_group_characteristic_line import DcmGroupCharacteristicLine -from dcmReader.dcm_characteristic_map import DcmCharacteristicMap -from dcmReader.dcm_fixed_characteristic_map import DcmFixedCharacteristicMap -from dcmReader.dcm_group_characteristic_map import DcmGroupCharacteristicMap -from dcmReader.dcm_distribution import DcmDistribution -from dcmReader.utils import _get_shape +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, TypeVar, Any + +import numpy as np + +from dcmReader.elements import ( + DcmFunction, + # DcmVariantCoding, + # DcmModuleHeader, + DcmParameter, + DcmParameterBlock, + DcmCharacteristicLine, + DcmFixedCharacteristicLine, + DcmGroupCharacteristicLine, + DcmCharacteristicMap, + DcmFixedCharacteristicMap, + DcmGroupCharacteristicMap, + DcmDistribution, +) +from dcmReader.utils import _SETTINGS if TYPE_CHECKING: - from dcmReader.utils import ArrayLike + from io import TextIOWrapper + + from dcmReader.utils import _DcmBase + + T_Element = TypeVar("T_Element", bound=_DcmBase) logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG) logger = logging.getLogger(__name__) @@ -31,63 +42,40 @@ comment_qualifier = ("!", "*", ".") +@dataclass class DcmReader: """Parser for the DCM (Data Conservation Format) format used by e.g. Vector, ETAS,...""" - def __init__(self): - self._file_header = "" - self._file_header_finished = False - self._functions_list = [] - self._parameter_list = [] - self._block_parameter_list = [] - self._characteristic_line_list = [] - self._fixed_characteristic_line_list = [] - self._group_characteristic_line_list = [] - self._characteristic_map_list = [] - self._fixed_characteristic_map_list = [] - self._group_characteristic_map_list = [] - self._distribution_list = [] - - self.parser_methods = { - "LANGNAME": {"key": "description", "method": self.parse_string}, - "DISPLAYNAME": {"key": "display_name", "method": self.parse_string}, - "FUNKTION": {"key": "function", "method": self.parse_string}, - "WERT": {"key": "", "method": self._parse_wert}, - "ST/X": {"key": "", "method": self._parse_coord_x}, - "ST/Y": {"key": "", "method": self._parse_coord_y}, - "EINHEIT_W": {"key": "units", "method": self.parse_string}, - "EINHEIT_X": {"key": "units_x", "method": self.parse_string}, - "EINHEIT_Y": {"key": "units_y", "method": self.parse_string}, - "VAR": {"key": "variants", "method": self.parse_string}, - "TEXT": {"key": "", "method": self._parse_text}, - # Data from comments: - "SSTX": {"key": "x_mapping", "method": self.parse_string}, - "SSTY": {"key": "y_mapping", "method": self.parse_string}, - } - self.parser_methods.update({k: {"key": "", "method": self._parse_comment} for k in comment_qualifier}) - - def _parse_wert(self, line: str, *, coord_x, coord_y, values: defaultdict, **kwargs): - # if not (coord_x or coord_y): - # raise ValueError(f"Values before stx/sty in {kwargs.get('name', 'Error')}") - - sty = coord_y[-1] if len(coord_y) > 0 else None - values[sty].extend(self.parse_block_parameters(line)) - - def _parse_text(self, line: str, *, coord_x, coord_y, values: defaultdict, **kwargs): - sty = coord_y[-1] if len(coord_y) > 0 else None - + _functions_list: list[DcmFunction] = field(repr=False, default_factory=list) + _parameter_list: list[DcmParameter] = field(repr=False, default_factory=list) + _block_parameter_list: list[DcmParameterBlock] = field(repr=False, default_factory=list) + _characteristic_line_list: list[DcmCharacteristicLine] = field(repr=False, default_factory=list) + _fixed_characteristic_line_list: list[DcmFixedCharacteristicLine] = field(repr=False, default_factory=list) + _group_characteristic_line_list: list[DcmGroupCharacteristicLine] = field(repr=False, default_factory=list) + _characteristic_map_list: list[DcmCharacteristicMap] = field(repr=False, default_factory=list) + _fixed_characteristic_map_list: list[DcmFixedCharacteristicMap] = field(repr=False, default_factory=list) + _group_characteristic_map_list: list[DcmGroupCharacteristicMap] = field(repr=False, default_factory=list) + _distribution_list: list[DcmDistribution] = field(repr=False, default_factory=list) + attrs: dict = field(default_factory=dict) + + def __post_init__(self) -> None: + self.parser_methods = _SETTINGS + + def _parse_wert(self, line: str, *, element: T_Element, **kwargs) -> None: + element.values = np.concatenate([element.values, self._parse_block_parameters(line)]) + + def _parse_text(self, line: str, *, element: T_Element, **kwargs) -> None: parameters = line.split(None, 1)[1] - # parameters = " ".join(parameters.split()).split() - parameters = [s.strip("\"'") for s in parameters.split()] - values[sty].extend(parameters) + parameters_list = [s.strip("\"'") for s in parameters.split()] + element.values = np.concatenate([element.values, parameters_list]) - def _parse_coord_x(self, line: str, *, coord_x: list, **kwargs) -> None: - coord_x.extend(self.parse_block_parameters(line)) + def _parse_coord_x(self, line: str, *, coord_x: list[float], **kwargs) -> None: + coord_x.extend(self._parse_block_parameters(line)) - def _parse_coord_y(self, line: str, *, coord_y: list, **kwargs) -> None: + def _parse_coord_y(self, line: str, *, coord_y: list[float], **kwargs) -> None: self._parse_coord_x(line, coord_x=coord_y) - def _parse_comment(self, line: str, *, attrs: dict, **kwargs): + def _parse_comment(self, line: str, *, element: T_Element, **kwargs) -> None: # TODO: Should the comment remember which row? So it can be reprinted there? cq, line_no_cq = line[0], line[1:].strip() @@ -96,48 +84,51 @@ def _parse_comment(self, line: str, *, attrs: dict, **kwargs): p = self.parser_methods.get(k, None) if p is not None: # The comment has known keyword, parse it accordingly: - parsed_values = p["method"](line_no_cq, attrs=attrs, **kwargs) + parsed_values = p["parse_method"](self)(line_no_cq, attrs=element.attrs, **kwargs) # Optionally store in attrs, otherwise assume it's # stored within the method: - if p["key"]: - attrs[p["key"]] = parsed_values + if p["parse_key"]: + element.attrs[p["parse_key"]] = parsed_values else: - cmnt_prev = attrs.get("comment", "") - attrs["comment"] = f"{cmnt_prev}{line_no_cq}{os.linesep}" + cmnt_prev = element.attrs.get("comment", "") + element.attrs["comment"] = f"{cmnt_prev}{line_no_cq}{os.linesep}" - def parse_variant(self, line, **kwargs): - """Parses a variant field + def _parse_string(self, line: str, **kwargs) -> str: + """Parses a text field Args: line (str): One line of the DCM file Returns: - Parsed variant either as float/int if variant is value - or as str if variant is text field + Parsed text field """ - variant = re.search(r"VAR\s+(.*?)=(.*)", line.strip()) - value = None - try: - value = self.convert_value(str(variant.group(2)).strip()) - except ValueError: - value = str(variant.group(2)).strip('" ') - return {str(variant.group(1)).strip(): value} - - @staticmethod - def parse_string(line, **kwargs): - """Parses a text field + return line.split(None, 1)[1].strip(' "') + def _parse_variant(self, line: str, **kwargs) -> dict: + """Parses a variant field Args: line (str): One line of the DCM file - Returns: - Parsed text field + Parsed variant either as float/int if variant is value + or as str if variant is text field """ - return line.split(None, 1)[1].strip(' "') - # return line.split(" ", 1)[1].strip(' "') + variant = re.search(r"VAR\s+(.*?)=(.*)", line.strip()) + value = None + if variant is not None: + key = str(variant.group(1)).strip() + value_str = str(variant.group(2)).strip() + try: + # Check if it's number + value = self._convert_value(value_str) + except ValueError: + value = value_str.strip('" ') + + return {key: value} + else: + return {} - def parse_block_parameters(self, line, **kwargs): + def _parse_block_parameters(self, line: str, **kwargs) -> list: """Parses a block parameters line Args: @@ -147,11 +138,10 @@ def parse_block_parameters(self, line, **kwargs): Parsed block parameters as list """ parameters = line.split(None, 1)[1] - parameters = " ".join(parameters.split()).split() - return [self.convert_value(i) for i in parameters] + parameters_list = " ".join(parameters.split()).split() + return [self._convert_value(i) for i in parameters_list] - @staticmethod - def convert_value(value): + def _convert_value(self, value: str) -> float: """Converts a text value to the correct number Args: @@ -172,17 +162,16 @@ def convert_value(value): except ValueError as err: raise ValueError(f"Cannot convert {value} from string to number.") from err - def parse_block_kennfeld(self, DcmThing, line_intro, dcm_file): + def _parse_elements(self, DcmElement: type[T_Element], line_intro: str, dcm_file: TextIOWrapper) -> T_Element: block_type, name, *shape_rev = line_intro.split() - # print(block_type, name) - dcm_thing = DcmThing(name=name, block_type=block_type) + element = DcmElement(name=name, block_type=block_type) # Reverse the order of x and y to match numpy: shape_rev.reverse() shape: tuple[int, ...] = tuple(int(v) for v in shape_rev if v != "@") - coord_y, coord_x = [], [] + coord_y: list[float] = [] + coord_x: list[float] = [] - values: defaultdict[float, list] = defaultdict(list) while True: line = dcm_file.readline().strip() @@ -190,10 +179,9 @@ def parse_block_kennfeld(self, DcmThing, line_intro, dcm_file): keyword = line.split(None, 1)[0] if keyword.startswith("END"): - if block_type == "STUETZSTELLENVERTEILUNG": - values[None] = coord_x - dcm_thing.attrs["x_mapping"] = dcm_thing.name + element.values = np.asarray(coord_x) + element.attrs["x_mapping"] = element.name # Handle coords and dims: xy = ("x", "y") @@ -202,37 +190,13 @@ def parse_block_kennfeld(self, DcmThing, line_intro, dcm_file): dims_rev = [] coords_rev = [] for i, v in enumerate(reversed(shape)): - dims_rev.append(dcm_thing.attrs.get(k_axis[i], f"{dcm_thing.name}_{xy[i]}")) + dims_rev.append(element.attrs.get(k_axis[i], f"{element.name}_{xy[i]}")) if crds_rev[i]: coords_rev.append(crds_rev[i]) - dcm_thing.dims = tuple(reversed(dims_rev)) - dcm_thing.coords = tuple(reversed(coords_rev)) - - # Handle values: - values_list = list(values.values()) - values_fin: ArrayLike - if len(values_list) == 1 and len(shape) == 0: - # constant single float - # squeeze out the wrapping list: - values_fin = values_list[0][0] - elif len(values) > 0 and len(shape) == 1: - # Tables: - values_fin = values_list[0] - elif len(values) > 0 and len(shape) > 1: - # Maps: - values_fin = values_list - else: - # Error? - values_fin = None - dcm_thing.values = values_fin - - # Check if the parsing went ok: - value_shape = _get_shape(dcm_thing.values) - if value_shape != shape: - logger.error( - f"The parsed shape, {value_shape}, does not match the " - f"expected shape, {shape}, from the '{block_type} {name}'-block" - ) + element.dims = tuple(reversed(dims_rev)) + element.coords = tuple(np.array(v) for v in reversed(coords_rev)) + + element.values = element.values.reshape(shape) break @@ -246,25 +210,53 @@ def parse_block_kennfeld(self, DcmThing, line_intro, dcm_file): if p is not None: # Parse the line: - parsed_values = p["method"]( - line, - # Used in functions: - coord_x=coord_x, - coord_y=coord_y, - values=values, - attrs=dcm_thing.attrs, - # Error handling: - name=dcm_thing.name, - ) + parsed_values = p["parse_method"](self)(line, coord_x=coord_x, coord_y=coord_y, element=element) # Optionally store in attrs, otherwise assume it's # stored within the method: - if p["key"]: - dcm_thing.attrs[p["key"]] = parsed_values + if p["parse_key"]: + element.attrs[p["parse_key"]] = parsed_values else: - logger.warning(f"JW: Unknown parameter field: {line=}{keyword=}") + logger.warning(f"Unknown parameter field: {line=}{keyword=}") + + return element + + def _parse_elements_funktionen( + self, DcmElement: type[T_Element], line_intro: str, dcm_file: TextIOWrapper + ) -> list[T_Element]: + # block_type = line_intro.strip() + + elements = [] + while True: + line = dcm_file.readline().strip() + + # Get the first keyword: + keyword = line.split(None, 1)[0] - return dcm_thing + if keyword.startswith("END"): + break + else: + function_match = re.search(r"FKT (.*?)(?: \"(.*?)?\"(?: \"(.*?)?\")?)?$", line.strip()) + block_type_short = "FKT" + + fm: dict[str, str] = {"name": "", "version": "", "description": ""} + for i, (k, v) in enumerate(fm.items()): + if function_match is not None: + fm[k] = function_match.group(i + 1) + + element = DcmElement(name=fm["name"], block_type=block_type_short) + element.attrs["version"] = fm["version"] + element.attrs["description"] = fm["description"] + element.attrs["_function"] = "FUNKTIONEN" + + # Add an empty array because you cannot init with + # np.array([]).shape=(0,) and not the correct (), np.concatenate doesn't + # work with shape=() though so can't use that as init either: + element.values = np.empty((), dtype=object) + + elements.append(element) + + return elements def write(self, file) -> None: """Writes the current DCM object to a dcm file @@ -284,8 +276,8 @@ def read(self, file) -> None: Args: file(str): DCM file to parse """ - _dcm_format = None - + file_header_finished = False + self.attrs["filename"] = file with open(file, "r", encoding="utf-8") as dcm_file: for line in dcm_file: # Remove whitespaces @@ -293,21 +285,22 @@ def read(self, file) -> None: # Check if line is comment if line.startswith(comment_qualifier): - if not self._file_header_finished: - self._file_header = self._file_header + line[1:].strip() + os.linesep + if not file_header_finished: + self.attrs["file_header"] = self.attrs.get("file_header", "") + line[1:].strip() + os.linesep continue # At this point first comment block passed - self._file_header_finished = True + file_header_finished = True # Check if empty line if line == "": continue # Check if format version line - if _dcm_format is None: - if line.startswith("KONSERVIERUNG_FORMAT"): - _dcm_format = float(re.search(r"(\d\.\d)", line.strip()).group(1)) + if self.attrs.get("dcm_format", None) is None: + kf, kf_value = line.split() + if kf == "KONSERVIERUNG_FORMAT": + self.attrs["dcm_format"] = float(kf_value) continue logging.info("Found line: %s", line) @@ -315,318 +308,51 @@ def read(self, file) -> None: # Check if functions start if line.startswith("FUNKTIONEN"): - while True: - line = dcm_file.readline() - if line.startswith("END"): - break - function_match = re.search(r"FKT (.*?)(?: \"(.*?)?\"(?: \"(.*?)?\")?)?$", line.strip()) - self._functions_list.append( - DcmFunction( - function_match.group(1), - function_match.group(2), - function_match.group(3), - ) - ) + self._functions_list.extend(self._parse_elements_funktionen(DcmFunction, line, dcm_file)) # Check if parameter starts elif line.startswith("FESTWERT "): - self._parameter_list.append(self.parse_block_kennfeld(DcmParameter, line, dcm_file)) - - # name = self.parse_string(line) - # found_parameter = DcmParameter(name) - # while True: - # line = dcm_file.readline().strip() - - # if line.startswith("END"): - # break - - # if line.startswith("LANGNAME"): - # found_parameter.description = self.parse_string(line) - # elif line.startswith("DISPLAYNAME"): - # found_parameter.display_name = self.parse_string(line) - # elif line.startswith("FUNKTION"): - # found_parameter.function = self.parse_string(line) - # elif line.startswith("WERT"): - # found_parameter.value = self.convert_value(line.split(" ", 1)[1].strip()) - # elif line.startswith("EINHEIT_W"): - # found_parameter.unit = self.parse_string(line) - # elif line.startswith("VAR"): - # found_parameter.variants.update(self.parse_variant(line)) - # elif line.startswith("TEXT"): - # found_parameter.text = self.parse_string(line) - # elif line.startswith(comment_qualifier): - # if found_parameter.comment is None: - # found_parameter.comment = line[1:].strip() + os.linesep - # else: - # found_parameter.comment += line[1:].strip() + os.linesep - # else: - # logger.warning("Unknown parameter field: %s", line) - - # self._parameter_list.append(found_parameter) + self._parameter_list.append(self._parse_elements(DcmParameter, line, dcm_file)) # Check if parameter block start elif line.startswith("FESTWERTEBLOCK"): - self._block_parameter_list.append(self.parse_block_kennfeld(DcmParameter, line, dcm_file)) - - # block_data = re.search(r"FESTWERTEBLOCK\s+(.*?)\s+(\d+)(?:\s+\@\s+(\d+))?", line.strip()) - # found_block_parameter = DcmParameterBlock(block_data.group(1)) - # found_block_parameter.x_dimension = self.convert_value(block_data.group(2)) - # found_block_parameter.y_dimension = ( - # self.convert_value(block_data.group(3)) if block_data.group(3) is not None else 1 - # ) - # while True: - # line = dcm_file.readline().strip() - # if line.startswith("END"): - # if len(found_block_parameter.values) != found_block_parameter.y_dimension: - # logger.error("Y dimension in %s do not match description!", found_block_parameter.name) - # break - - # if line.startswith("LANGNAME"): - # found_block_parameter.description = self.parse_string(line) - # elif line.startswith("DISPLAYNAME"): - # found_block_parameter.display_name = self.parse_string(line) - # elif line.startswith("FUNKTION"): - # found_block_parameter.function = self.parse_string(line) - # elif line.startswith("WERT"): - # parameters = self.parse_block_parameters(line) - # if len(parameters) != found_block_parameter.x_dimension: - # logger.error("X dimension in %s do not match description!", found_block_parameter.name) - # found_block_parameter.values.append(parameters) - # elif line.startswith("EINHEIT_W"): - # found_block_parameter.unit = self.parse_string(line) - # elif line.startswith("VAR"): - # found_block_parameter.variants.update(self.parse_variant(line)) - # elif line.startswith(comment_qualifier): - # if found_block_parameter.comment is None: - # found_block_parameter.comment = line[1:].strip() + os.linesep - # else: - # found_block_parameter.comment += line[1:].strip() + os.linesep - # else: - # logger.warning("Unknown parameter field: %s", line) - - # self._block_parameter_list.append(found_block_parameter) + self._block_parameter_list.append(self._parse_elements(DcmParameterBlock, line, dcm_file)) # Check if characteristic line elif line.startswith("KENNLINIE"): - re_match = re.search(r"KENNLINIE\s+(.*?)\s+(\d+)", line.strip()) - found_characteristic_line = DcmCharacteristicLine(re_match.group(1)) - found_characteristic_line.x_dimension = self.convert_value(re_match.group(2)) - parameters = [] - stx = [] - - while True: - line = dcm_file.readline().strip() - if line.startswith("END"): - if len(stx) != found_characteristic_line.x_dimension: - logger.error( - "X dimension in %s \ - do not match description!", - found_characteristic_line.name, - ) - if len(parameters) != found_characteristic_line.x_dimension: - logger.error( - "Values dimension in %s \ - do not match description!", - found_characteristic_line.name, - ) - found_characteristic_line.values = dict(zip(stx, parameters)) - break - if line.startswith("LANGNAME"): - found_characteristic_line.description = self.parse_string(line) - elif line.startswith("DISPLAYNAME"): - found_characteristic_line.display_name = self.parse_string(line) - elif line.startswith("FUNKTION"): - found_characteristic_line.function = self.parse_string(line) - elif line.startswith("WERT"): - parameters.extend(self.parse_block_parameters(line)) - elif line.startswith("ST/X"): - stx.extend(self.parse_block_parameters(line)) - elif line.startswith("EINHEIT_W"): - found_characteristic_line.unit_values = self.parse_string(line) - elif line.startswith("EINHEIT_X"): - found_characteristic_line.unit_x = self.parse_string(line) - elif line.startswith("VAR"): - found_characteristic_line.variants.update(self.parse_variant(line)) - elif line.startswith(comment_qualifier): - re_match = re.search(r"SSTX\s+(.*)", line) - if re_match: - found_characteristic_line.x_mapping = re_match.group(1) - else: - if found_characteristic_line.comment is None: - found_characteristic_line.comment = line[1:].strip() + os.linesep - else: - found_characteristic_line.comment += line[1:].strip() + os.linesep - else: - logger.warning("Unknown parameter field: %s", line) - - self._characteristic_line_list.append(found_characteristic_line) + self._characteristic_line_list.append(self._parse_elements(DcmCharacteristicLine, line, dcm_file)) # Check if fixed characteristic line elif line.startswith("FESTKENNLINIE"): - re_match = re.search(r"FESTKENNLINIE\s+(.*?)\s+(\d+)", line.strip()) - found_fixed_characteristic_line = DcmFixedCharacteristicLine(re_match.group(1)) - found_fixed_characteristic_line.x_dimension = self.convert_value(re_match.group(2)) - parameters = [] - stx = [] - - while True: - line = dcm_file.readline().strip() - if line.startswith("END"): - if len(stx) != found_fixed_characteristic_line.x_dimension: - logger.error( - "X dimension in %s do not match description!", found_fixed_characteristic_line.name - ) - if len(parameters) != found_fixed_characteristic_line.x_dimension: - logger.error( - "Values dimension in %s \ - do not match description!", - found_fixed_characteristic_line.name, - ) - found_fixed_characteristic_line.values = dict(zip(stx, parameters)) - break - - if line.startswith("LANGNAME"): - found_fixed_characteristic_line.description = self.parse_string(line) - elif line.startswith("DISPLAYNAME"): - found_fixed_characteristic_line.display_name = self.parse_string(line) - elif line.startswith("FUNKTION"): - found_fixed_characteristic_line.function = self.parse_string(line) - elif line.startswith("WERT"): - parameters.extend(self.parse_block_parameters(line)) - elif line.startswith("ST/X"): - stx.extend(self.parse_block_parameters(line)) - elif line.startswith("EINHEIT_W"): - found_fixed_characteristic_line.unit_values = self.parse_string(line) - elif line.startswith("EINHEIT_X"): - found_fixed_characteristic_line.unit_x = self.parse_string(line) - elif line.startswith("VAR"): - found_fixed_characteristic_line.variants.update(self.parse_variant(line)) - elif line.startswith(comment_qualifier): - re_match = re.search(r"SSTX\s+(.*)", line) - if re_match: - found_fixed_characteristic_line.x_mapping = re_match.group(1) - else: - if found_fixed_characteristic_line.comment is None: - found_fixed_characteristic_line.comment = line[1:].strip() + os.linesep - else: - found_fixed_characteristic_line.comment += line[1:].strip() + os.linesep - else: - logger.warning("Unknown parameter field: %s", line) - - self._fixed_characteristic_line_list.append(found_fixed_characteristic_line) + self._fixed_characteristic_line_list.append( + self._parse_elements(DcmFixedCharacteristicLine, line, dcm_file) + ) # Check if group characteristic line elif line.startswith("GRUPPENKENNLINIE"): - re_match = re.search(r"GRUPPENKENNLINIE\s+(.*?)\s+(\d+)", line.strip()) - found_group_characteristic_line = DcmGroupCharacteristicLine(re_match.group(1)) - found_group_characteristic_line.x_dimension = self.convert_value(re_match.group(2)) - parameters = [] - stx = [] - - while True: - line = dcm_file.readline().strip() - if line.startswith("END"): - if len(parameters) != found_group_characteristic_line.x_dimension: - logger.error( - "Values dimension in %s \ - do not match description!", - found_group_characteristic_line.name, - ) - if len(stx) != found_group_characteristic_line.x_dimension: - logger.error( - "X dimension in %s \ - do not match description!", - found_group_characteristic_line.name, - ) - found_group_characteristic_line.values = dict(zip(stx, parameters)) - break - - if line.startswith("LANGNAME"): - found_group_characteristic_line.description = self.parse_string(line) - elif line.startswith("DISPLAYNAME"): - found_group_characteristic_line.display_name = self.parse_string(line) - elif line.startswith("FUNKTION"): - found_group_characteristic_line.function = self.parse_string(line) - elif line.startswith("WERT"): - parameters.extend(self.parse_block_parameters(line)) - elif line.startswith("ST/X"): - stx.extend(self.parse_block_parameters(line)) - elif line.startswith("EINHEIT_W"): - found_group_characteristic_line.unit_values = self.parse_string(line) - elif line.startswith("EINHEIT_X"): - found_group_characteristic_line.unit_x = self.parse_string(line) - elif line.startswith("VAR"): - found_group_characteristic_line.variants.update(self.parse_variant(line)) - elif line.startswith(comment_qualifier): - re_match = re.search(r"SSTX\s+(.*)", line) - if re_match: - found_group_characteristic_line.x_mapping = re_match.group(1) - else: - if found_group_characteristic_line.comment is None: - found_group_characteristic_line.comment = line[1:].strip() + os.linesep - else: - found_group_characteristic_line.comment += line[1:].strip() + os.linesep - else: - logger.warning("Unknown parameter field: %s", line) - - self._group_characteristic_line_list.append(found_group_characteristic_line) + self._group_characteristic_line_list.append( + self._parse_elements(DcmGroupCharacteristicLine, line, dcm_file) + ) # Check for characteristic map elif line.startswith("KENNFELD "): - self._characteristic_map_list.append( - self.parse_block_kennfeld(DcmCharacteristicMap, line, dcm_file) - ) + self._characteristic_map_list.append(self._parse_elements(DcmCharacteristicMap, line, dcm_file)) # Check for fixed characteristic map elif line.startswith("FESTKENNFELD "): self._fixed_characteristic_map_list.append( - self.parse_block_kennfeld(DcmFixedCharacteristicMap, line, dcm_file) + self._parse_elements(DcmFixedCharacteristicMap, line, dcm_file) ) # Check for group characteristic map elif line.startswith("GRUPPENKENNFELD "): self._group_characteristic_map_list.append( - self.parse_block_kennfeld(DcmGroupCharacteristicMap, line, dcm_file) + self._parse_elements(DcmGroupCharacteristicMap, line, dcm_file) ) # Check if distribution elif line.startswith("STUETZSTELLENVERTEILUNG"): - self._distribution_list.append(self.parse_block_kennfeld(DcmDistribution, line, dcm_file)) - - # re_match = re.search(r"STUETZSTELLENVERTEILUNG\s+(.*?)\s+(\d+)", line.strip()) - # found_distribution = DcmDistribution(re_match.group(1)) - # found_distribution.x_dimension = self.convert_value(re_match.group(2)) - # parameters = None - # stx = None - - # while True: - # line = dcm_file.readline().strip() - # if line.startswith("END"): - # if len(found_distribution.values) != found_distribution.x_dimension: - # logger.error("X dimension in %s do not match description!", found_distribution.name) - # break - - # if line.startswith("LANGNAME"): - # found_distribution.description = self.parse_string(line) - # elif line.startswith("DISPLAYNAME"): - # found_distribution.display_name = self.parse_string(line) - # elif line.startswith("FUNKTION"): - # found_distribution.function = self.parse_string(line) - # elif line.startswith("ST/X"): - # found_distribution.values.extend(self.parse_block_parameters(line)) - # elif line.startswith("EINHEIT_X"): - # found_distribution.unit_x = self.parse_string(line) - # elif line.startswith("VAR"): - # found_distribution.variants.update(self.parse_variant(line)) - # elif line.startswith(comment_qualifier): - # if found_distribution.comment is None: - # found_distribution.comment = line[1:].strip() + os.linesep - # else: - # found_distribution.comment += line[1:].strip() + os.linesep - # else: - # logger.warning("Unknown parameter field: %s", line) - - # self._distribution_list.append(found_distribution) + self._distribution_list.append(self._parse_elements(DcmDistribution, line, dcm_file)) # Unknown start of line else: @@ -675,7 +401,7 @@ def get_distributions(self) -> list: def __str__(self) -> str: output_string = "" # Print the file header - for line in self._file_header.splitlines(True): + for line in self.attrs.get("file_header", "").splitlines(True): output_string += f"* {line}" # Print the file version @@ -688,7 +414,7 @@ def __str__(self) -> str: output_string += "END\n\n" # Print rest of DCM objects - object_list = [] + object_list: list[_DcmBase] = [] object_list.extend(self._parameter_list) object_list.extend(self._block_parameter_list) object_list.extend(self._characteristic_line_list) From 80c4b67720786bb2a26acf1dc5afc1acc6829abd Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Fri, 27 Jan 2023 23:19:12 +0100 Subject: [PATCH 18/28] use ndarray's, generalize print format --- src/dcmReader/utils.py | 318 ++++++++++++++++++++++++++--------------- 1 file changed, 200 insertions(+), 118 deletions(-) diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py index d9839d9..960f2c4 100644 --- a/src/dcmReader/utils.py +++ b/src/dcmReader/utils.py @@ -10,65 +10,32 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, field +import numpy as np + if TYPE_CHECKING: - from typing import Union - - ArrayLike = float | str | list[float | str] | list[list[float | str]] - - -def _get_shape(array_like: ArrayLike, *, check_if_ragged: bool = False) -> tuple[int, ...]: - """ - Get the shape of array_like. - - Examples - -------- - >>> _get_shape(1) - () - >>> _get_shape([]) - (0,) - >>> _get_shape([1]) - (1,) - >>> _get_shape([[1]]) - (1, 1) - >>> _get_shape([[1], [2]]) - (2, 1) - - Calculating the shape from a ragged sequence does not work. It's possible to check - for these inconsistencies although at a cost of performance: - - >>> try: - >>> _get_shape([[0, 1, 2], [4, 5]], check_if_ragged=True) - >>> except ValueError as e: - >>> msg = str(e) - >>> print(f"{msg[:48]} (...) {msg[-17:]}") - >>> _get_shape([[0, 1, 2], [4, 5]], check_if_ragged=True) - Calculating a shape from ragged nested sequences (...) is not supported. - """ - # https://stackoverflow.com/questions/51960857/how-can-i-get-a-list-shape-without-using-numpy - if isinstance(array_like, list): - # More dimensions, so make a recursive call - outermost_size = len(array_like) - if outermost_size == 0: - return (outermost_size,) - else: - if check_if_ragged: - # This costs some - it = iter(array_like) - the_len = len(next(it)) - if not all(len(l) == the_len for l in it): - raise ValueError( - "Calculating a shape from ragged nested sequences " - "(which is a list-or-tuple of lists-or-tuples " - "with different lengths or shapes) is not supported." - ) - return (outermost_size, *_get_shape(array_like[0])) - else: - # No more dimensions, so we're done - return () + from typing import Union, Any, Callable, TypedDict + + from numpy.typing import ArrayLike + + T_Fmt = Callable[Any, str] + + class T_Setting(TypedDict): + key_ger: str + key_eng: str + print_key: str + print_format: T_Fmt + parse_key: str + parse_method: Callable + + T_Settings = dict[str, T_Setting] + + +def fmt_base(x): + return f"{x}" class _HasValuesProtocol(Protocol): - values: ArrayLike + values: np.ndarray class ShapeRelatedMixin(_HasValuesProtocol): @@ -81,7 +48,7 @@ def shape(self) -> tuple[int, ...]: -------- numpy.ndarray.shape """ - return _get_shape(self.values) + return self.values.shape @property def ndim(self) -> int: @@ -92,7 +59,7 @@ def ndim(self) -> int: -------- numpy.ndarray.ndim """ - return len(self.shape) + return self.values.ndim @property def size(self) -> int: @@ -103,24 +70,28 @@ def size(self) -> int: -------- numpy.ndarray.size """ - return math.prod(self.shape) + return self.values.size def __len__(self) -> int: - try: - return self.shape[0] - except IndexError: - raise TypeError("len() of unsized object") + return len(self.values) -def _to_str(val: str | int | float | list, delimiter: str = " ") -> str: - val_list: list = [val] if isinstance(val, (str, int, float)) else val - return delimiter.join(map(str, val_list)) +def _to_str(val: ArrayLike, fmt: T_Fmt = fmt_base, delimiter: str = " ") -> str: + val_list = np.atleast_1d(val) + # val_list = np.asarray(val) + # return np.array2string(val_list, separator=delimiter).strip("[]") # will use '' :( + # print(val_list) + # fmt = lambda x: f'"{x}"' if isinstance(x, str) else f"{x}" + out = delimiter.join(map(fmt, val_list)) + return out + # return delimiter.join(val_list) -def _print_values(key: str, value: str | int | float | list, n: int = 6, pad: int = 14) -> str: +def _print_values(key: str, value: ArrayLike, fmt: T_Fmt = fmt_base, n: int = 6, pad: int = 14) -> str: """Print pairwise values prettily.""" # Normalize so that value is always a list: - value_list: list = [value] if isinstance(value, (str, int, float)) else value + value_list = np.atleast_1d(value) + # value_list = np.asarray(value) # Split too long lists into chunks. value_list_chunked = [value_list[i : i + n] for i in range(0, len(value_list), n)] @@ -129,93 +100,204 @@ def _print_values(key: str, value: str | int | float | list, n: int = 6, pad: in pad_ = f"<{pad}" out = "" for v in value_list_chunked: - out += f" {key:{pad_}}{_to_str(v)}\n" + out += f" {key:{pad_}}{_to_str(v, fmt)}\n" return out -def _attrs_init() -> dict: - return { - "description": "", - "display_name": "", - "variants": {}, - "text": "", - "function": "", - "units_x": "", - "units_y": "", - "units": "", +_COMMENT_QUALIFIER = ("!", "*", ".") + +_SETTINGS: T_Settings = { + "LANGNAME": { + "key_ger": "LANGNAME", + "key_eng": "description", + "print_key": "LANGNAME", + "print_format": lambda x: f'"{x}"', + "parse_key": "description", + "parse_method": lambda self: self._parse_string, + }, + "FUNKTION": { + "key_ger": "FUNKTION", + "key_eng": "function", + "print_key": "FUNKTION", + "print_format": lambda x: f"{x}", + "parse_key": "function", + "parse_method": lambda self: self._parse_string, + }, + "DISPLAYNAME": { + "key_ger": "DISPLAYNAME", + "key_eng": "display_name", + "print_key": "DISPLAYNAME", + "print_format": lambda x: f'"{x}"', + "parse_key": "display_name", + "parse_method": lambda self: self._parse_string, + }, + "EINHEIT_X": { + "key_ger": "EINHEIT_X", + "key_eng": "units_x", + "print_key": "EINHEIT_X", + "print_format": lambda x: f'"{x}"', + "parse_key": "units_x", + "parse_method": lambda self: self._parse_string, + }, + "EINHEIT_Y": { + "key_ger": "EINHEIT_Y", + "key_eng": "units_y", + "print_key": "EINHEIT_Y", + "print_format": lambda x: f'"{x}"', + "parse_key": "units_y", + "parse_method": lambda self: self._parse_string, + }, + "EINHEIT_W": { + "key_ger": "EINHEIT_W", + "key_eng": "units", + "print_key": "EINHEIT_W", + "print_format": lambda x: f'"{x}"', + "parse_key": "units", + "parse_method": lambda self: self._parse_string, + }, + "ST/X": { + "key_ger": "ST/X", + "key_eng": "ST/X", + "print_key": "ST/X", + "print_format": lambda x: f"{x}", + "parse_key": "", + "parse_method": lambda self: self._parse_coord_x, + }, + "ST/Y": { + "key_ger": "ST/Y", + "key_eng": "ST/Y", + "print_key": "ST/Y", + "print_format": lambda x: f"{x}", + "parse_key": "", + "parse_method": lambda self: self._parse_coord_y, + }, + "SSTX": { + # From comments: + "key_ger": "SSTX", + "key_eng": "x_mapping", + "print_key": "*SSTY", + "print_format": lambda x: f"{x}", + "parse_key": "x_mapping", + "parse_method": lambda self: self._parse_string, + }, + "SSTY": { + # From comments: + "key_ger": "SSTY", + "key_eng": "y_mapping", + "print_key": "*SSTY", + "print_format": lambda x: f"{x}", + "parse_key": "y_mapping", + "parse_method": lambda self: self._parse_string, + }, + "WERT": { + "key_ger": "WERT", + "key_eng": "value", + "print_key": "WERT", + "print_format": lambda x: f"{x}", + "parse_key": "", + "parse_method": lambda self: self._parse_wert, + }, + "TEXT": { + "key_ger": "TEXT", + "key_eng": "text", + "print_key": "TEXT", + "print_format": lambda x: f'"{x}"', + "parse_key": "", + "parse_method": lambda self: self._parse_text, + }, + "VAR": { + "key_ger": "VAR", + "key_eng": "variants", + "print_key": "VAR", + "print_format": lambda x: ", ".join([f'{k}="{v}"' if isinstance(v, str) else f"{k}={v}" for k, v in x.items()]), + "parse_key": "variants", + "parse_method": lambda self: self._parse_variant, + }, +} +_SETTINGS.update( + { + k: { + "key_ger": k, + "key_eng": k, + # "print_key": k, + "print_format": lambda x: f'"{x}"', + # "print_kwargs": {"pad": 0, "n": 1}, + "parse_key": "", + "parse_method": lambda self: self._parse_comment, + } + for k in _COMMENT_QUALIFIER } +) @dataclass class _DcmBase(ShapeRelatedMixin): name: str - values: ArrayLike = field(default_factory=list) - coords: tuple[ArrayLike, ...] = field(default_factory=tuple) + values: np.ndarray = field(default_factory=lambda: np.array([])) + coords: tuple[np.ndarray, ...] = field(default_factory=tuple) dims: tuple[str, ...] = field(default_factory=tuple) - attrs: dict = field(default_factory=_attrs_init) + attrs: dict = field(default_factory=dict) block_type: str = "" def __lt__(self, other): - self_function = self.attrs.get("function", None) - self_description = self.attrs.get("descrition", None) - other_function = other.attrs.get("function", None) - other_description = other.attrs.get("descrition", None) + self_function = self.attrs.get("function", "") + self_description = self.attrs.get("description", "") + other_function = other.attrs.get("function", "") + other_description = other.attrs.get("description", "") return self_function < other_function and self_description < other_description + def _print_attrs(self, k: str, value: str) -> str: + setting = _SETTINGS[k] + key_eng = setting["key_eng"] + + attrs_value = self.attrs.get(key_eng, "") + if attrs_value: + print_key = setting["print_key"] + print_val = attrs_value + print_format = setting["print_format"] + kws: dict[str, Any] = {} # TODO: Remove? Currently only needed if comments were printed. + + value += _print_values(print_key, print_val, print_format, **kws) + + return value + def _print_dcm_format(self) -> str: """ Print the data according to the dcm-format. """ - if self.block_type == "FUNKTIONEN": - return f'{self.block_type} {self.name} "{self.version}" "{self.description}"' - ndim = self.ndim shape_rev: list[int | str] = list(reversed(self.shape)) if self.block_type == "FESTWERTEBLOCK" and ndim == 2: shape_rev.insert(1, "@") coords_rev = list(reversed(self.coords)) + ncoords = len(coords_rev) # Header: value = f"{self.block_type} {self.name} {_to_str(shape_rev)}\n" # Attributes printed before the values: - ks = ( - # TODO: Should comments be reprinted? They have currently lost the position - # so the comment might be out of context. - # ("*", "comment", lambda x: f"{', '.join(x.split(os.linesep))}", {"pad": 0, "n": 1}), - ("LANGNAME", "description", lambda x: f'"{x}"', {}), - ("FUNKTION", "function", lambda x: f"{x}", {}), - ("DISPLAYNAME", "display_name", lambda x: f'"{x}"', {}), - ("EINHEIT_X", "units_x", lambda x: f'"{x}"', {}), - ("EINHEIT_Y", "units_y", lambda x: f'"{x}"', {}), - ("EINHEIT_W", "units", lambda x: f'"{x}"', {}), - ("*SSTX", "x_mapping", lambda x: f"{x}", {}), - ("*SSTY", "y_mapping", lambda x: f"{x}", {}), - # Printed after WERT: - ("TEXT", "text", lambda x: f"{x}", {}), # Is this equivalent to WERT? - ("VAR", "variants", lambda x: f"{x}", {}), - ) - idx_as_suffx = 2 - for k, v, f, kws in ks[:-idx_as_suffx]: - if self.attrs.get(v, ""): - value += _print_values(k, f"{f(self.attrs[v])}", **kws) + for k in _SETTINGS.keys(): + if k in ("WERT", "ST/X", "ST/Y", "VAR"): + continue + + value = self._print_attrs(k, value) # x-values: - if ndim > 0: - value += _print_values("ST/X", coords_rev[0]) + if ncoords > 0: + value += _print_values("ST/X", coords_rev[0], _SETTINGS["ST/X"]["print_format"]) # y-value and values: - for i, val in enumerate(self.values if ndim > 1 else [self.values]): - if ndim > 1: - value += _print_values("ST/Y", coords_rev[1][i]) + print_key_val = "TEXT" if self.values.dtype.kind in ("S", "U") else "WERT" + for i, val in enumerate(np.atleast_2d(self.values)): + if ncoords > 1: + value += _print_values("ST/Y", coords_rev[1][i], _SETTINGS["ST/Y"]["print_format"]) - value += _print_values("WERT", val) + value += _print_values(print_key_val, val, _SETTINGS[print_key_val]["print_format"]) - # Attributes printed after the values: - for k, v, f, kws in ks[-idx_as_suffx:]: - if self.attrs.get(v, ""): - value += _print_values(k, f"{f(self.attrs[v])}", **kws) + # # Attributes printed after the values: + for k in ("VAR",): + value = self._print_attrs(k, value) # Close: value += "END" From ec7974ce6f765f56676b37f0359fe018303fd1ff Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Fri, 27 Jan 2023 23:20:45 +0100 Subject: [PATCH 19/28] move to elements --- src/dcmReader/dcm_characteristic_line.py | 74 ------- src/dcmReader/dcm_characteristic_map.py | 44 ---- src/dcmReader/dcm_distribution.py | 39 ---- .../dcm_fixed_characteristic_line.py | 18 -- src/dcmReader/dcm_fixed_characteristic_map.py | 10 - src/dcmReader/dcm_function.py | 28 --- .../dcm_group_characteristic_line.py | 17 -- src/dcmReader/dcm_group_characteristic_map.py | 10 - src/dcmReader/dcm_parameter.py | 37 ---- src/dcmReader/dcm_parameter_block.py | 101 ---------- src/dcmReader/elements.py | 190 ++++++++++++++++++ 11 files changed, 190 insertions(+), 378 deletions(-) delete mode 100644 src/dcmReader/dcm_characteristic_line.py delete mode 100644 src/dcmReader/dcm_characteristic_map.py delete mode 100644 src/dcmReader/dcm_distribution.py delete mode 100644 src/dcmReader/dcm_fixed_characteristic_line.py delete mode 100644 src/dcmReader/dcm_fixed_characteristic_map.py delete mode 100644 src/dcmReader/dcm_function.py delete mode 100644 src/dcmReader/dcm_group_characteristic_line.py delete mode 100644 src/dcmReader/dcm_group_characteristic_map.py delete mode 100644 src/dcmReader/dcm_parameter.py delete mode 100644 src/dcmReader/dcm_parameter_block.py create mode 100644 src/dcmReader/elements.py diff --git a/src/dcmReader/dcm_characteristic_line.py b/src/dcmReader/dcm_characteristic_line.py deleted file mode 100644 index 0e7948f..0000000 --- a/src/dcmReader/dcm_characteristic_line.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Definition of DCM characteristic line -""" -from dataclasses import dataclass - - -@dataclass -class DcmCharacteristicLine: - """Definition of a characteristic line - - Attributes: - name (str): Name of the characteristic line - description (str): Description of the characteristic line, started by LANGNAME in DCM - display_name (str): Characteristic line name according asam-2mc, started by DISPLAYNAME in DCM - variants (dict): Variants for the characteristic line, started by VAR in DCM - function (str): Name of the assigned function, started by FUNKTION in DCM - unit_x (str): Unit of the x axis values, started by EINHEIT_X in DCM - unit_values (str): Unit of the values, started by EINHEIT_W in DCM - values (dict): Dict of values of the parameter, KEYs are retrieved from ST/X, - values are retrieved from WERT - x_dimension (int): Dimension in x direction of the parameter block - x_mapping (str): Mapping of the x axis to a distribution, if available as a comment in DCM - comment (str): Block comment - """ - - def __init__(self, name) -> None: - self.name = name - self.values = {} - self.description = None - self.display_name = None - self.variants = {} - self.function = None - self.unit_x = None - self.unit_values = None - self.x_dimension = 0 - self.x_mapping = None - self.comment = None - self._type_name = "KENNLINIE" - - def __str__(self): - value = f"{self._type_name} {self.name} {self.x_dimension}\n" - - if self.comment: - for line in self.comment.splitlines(True): - value += f"* {line}" - if self.description: - value += f' LANGNAME "{self.description}"\n' - if self.function: - value += f' FUNKTION "{self.function}"\n' - if self.display_name: - value += f" DISPLAYNAME {self.display_name}\n" - if self.unit_x: - value += f' EINHEIT_X "{self.unit_x}"\n' - if self.unit_values: - value += f' EINHEIT_W "{self.unit_values}"\n' - if self.x_mapping: - value += f'*SSTX {self.x_mapping}\n' - if self.values: - x_entries = "" - value_entries = "" - for x_entry, value_entry in self.values.items(): - x_entries += f"{str(x_entry)} " - value_entries += f"{str(value_entry)} " - value += f' ST/X {x_entries.strip()}\n' - value += f' WERT {value_entries.strip()}\n' - for var_name, var_value in self.variants.items(): - value += f" VAR {var_name}={var_value}\n" - - value += "END" - - return value - - def __lt__(self, other): - return self.function < other.function and self.description < other.description diff --git a/src/dcmReader/dcm_characteristic_map.py b/src/dcmReader/dcm_characteristic_map.py deleted file mode 100644 index b1a2ca2..0000000 --- a/src/dcmReader/dcm_characteristic_map.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Definition of DCM characteristic map -""" -from __future__ import annotations - -from dataclasses import dataclass, field - -from dcmReader.utils import _DcmBase - - -def _attrs_init() -> dict: - return { - "description": "", - "display_name": "", - "variants": {}, - "function": "", - "units_x": "", - "units_y": "", - "units": "", - } - - -@dataclass -class DcmCharacteristicMap(_DcmBase): - """Definition of a characteristic map - - Attributes: - name (str): Name of the characteristic map - description (str): Description of the characteristic map, started by LANGNAME in DCM - display_name (str): Characteristic map name according asam-2mc, started by DISPLAYNAME in DCM - variants (dict): Variants for the characteristic map, started by VAR in DCM - function (str): Name of the assigned function, started by FUNKTION in DCM - unit_x (str): Unit of the x axis values, started by EINHEIT_X in DCM - unit_y (str): Unit of the y axis values, started by EINHEIT_Y in DCM - unit_values (str): Unit of the values, started by EINHEIT_W in DCM - values (dict): 2D Dict of values of the parameter - The inner dict contains the values from ST/X as keys and the - values retrieved from WERT as values. The keys of the outer dict - contains the values from ST/Y. - x_dimension (int): Dimension in x direction of the characteristic maps - y_dimension (int): Dimension in y direction of the characteristic maps - """ - - attrs: dict = field(default_factory=_attrs_init) diff --git a/src/dcmReader/dcm_distribution.py b/src/dcmReader/dcm_distribution.py deleted file mode 100644 index ef3410e..0000000 --- a/src/dcmReader/dcm_distribution.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Definition of DCM distribution -""" -from __future__ import annotations - -from dataclasses import dataclass, field - -from dcmReader.utils import _DcmBase - - -def _attrs_init() -> dict: - return { - "description": "", - "display_name": "", - "variants": {}, - "function": "", - "units_x": "", - # "units": "", - "comment": "", - } - - -@dataclass -class DcmDistribution(_DcmBase): - """Definition of a distribution - - Attributes: - name (str): Name of the distribution - description (str): Description of the distribution, started by LANGNAME in DCM - display_name (str): Distribution name according asam-2mc, started by DISPLAYNAME in DCM - variants (dict): Variants for the distribution, started by VAR in DCM - function (str): Name of the assigned function, started by FUNKTION in DCM - unit_x (str): Unit of the x axis values, started by EINHEIT_X in DCM - values (list): List of values of the distribution, values are retrieved from WERT - x_dimension (int): Dimension in x direction of the distribution - comment (str): Block comment - """ - - attrs: dict = field(default_factory=_attrs_init) diff --git a/src/dcmReader/dcm_fixed_characteristic_line.py b/src/dcmReader/dcm_fixed_characteristic_line.py deleted file mode 100644 index 64ddfa4..0000000 --- a/src/dcmReader/dcm_fixed_characteristic_line.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Definition of DCM fixed characteristic line -""" - -from dataclasses import dataclass -from dcmReader.dcm_characteristic_line import DcmCharacteristicLine - - -@dataclass -class DcmFixedCharacteristicLine(DcmCharacteristicLine): - """Definition of a fixed characteristic line, derived from characteristic line""" - - def __init__(self, name) -> None: - super().__init__(name) - self._type_name = "FESTKENNLINIE" - - def __lt__(self, other): - return self.function < other.function and self.description < other.description diff --git a/src/dcmReader/dcm_fixed_characteristic_map.py b/src/dcmReader/dcm_fixed_characteristic_map.py deleted file mode 100644 index bb969e2..0000000 --- a/src/dcmReader/dcm_fixed_characteristic_map.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Definition of DCM fixed characteristic map -""" -from __future__ import annotations - -from dcmReader.dcm_characteristic_map import DcmCharacteristicMap - - -class DcmFixedCharacteristicMap(DcmCharacteristicMap): - """Definition of a fixed characteristic map, derived from characteristic map""" diff --git a/src/dcmReader/dcm_function.py b/src/dcmReader/dcm_function.py deleted file mode 100644 index 08e7373..0000000 --- a/src/dcmReader/dcm_function.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Definition of DCM function""" -from dataclasses import dataclass - - -@dataclass -class DcmFunction: - """Definition of a function - - Attributes: - name (str): Name of the function - version (str): Version number of the function - description (str): Description of the function - """ - - name = None - description = None - version = None - - def __init__(self, name, version, description): - self.name = name - self.version = version - self.description = description - - def __str__(self): - return f'FKT {self.name} "{self.version}" "{self.description}"' - - def __lt__(self, other): - return self.name < other.name diff --git a/src/dcmReader/dcm_group_characteristic_line.py b/src/dcmReader/dcm_group_characteristic_line.py deleted file mode 100644 index a518187..0000000 --- a/src/dcmReader/dcm_group_characteristic_line.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Definition of DCM group characteristic line -""" -from dataclasses import dataclass -from dcmReader.dcm_characteristic_line import DcmCharacteristicLine - - -@dataclass -class DcmGroupCharacteristicLine(DcmCharacteristicLine): - """Definition of a group characteristic line, derived from characteristic line""" - - def __init__(self, name) -> None: - super().__init__(name) - self._type_name = "GRUPPENKENNLINIE" - - def __lt__(self, other): - return self.function < other.function and self.description < other.description diff --git a/src/dcmReader/dcm_group_characteristic_map.py b/src/dcmReader/dcm_group_characteristic_map.py deleted file mode 100644 index e5734b9..0000000 --- a/src/dcmReader/dcm_group_characteristic_map.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Definition of DCM fixed characteristic map -""" -from __future__ import annotations - -from dcmReader.dcm_characteristic_map import DcmCharacteristicMap - - -class DcmGroupCharacteristicMap(DcmCharacteristicMap): - """Definition of a group characteristic map, derived from characteristic map""" diff --git a/src/dcmReader/dcm_parameter.py b/src/dcmReader/dcm_parameter.py deleted file mode 100644 index 39dcd4b..0000000 --- a/src/dcmReader/dcm_parameter.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Definition of DCM parameter""" -from __future__ import annotations - -from dataclasses import dataclass, field - -from dcmReader.utils import _DcmBase - - -def _attrs_init() -> dict: - return { - "description": "", - "display_name": "", - "variants": {}, - "function": "", - "units_x": "", - "units_y": "", - "units": "", - } - - -@dataclass -class DcmParameter(_DcmBase): - """Definition of a parameter - - Attributes: - name (str): Name of the parameter - description (str): Description of the parameter, started by LANGNAME in DCM - display_name (str): Parameter name according asam-2mc, started by DISPLAYNAME in DCM - variants (dict): Variants for the parameter, started by VAR in DCM - function (str): Name of the assigned function, started by FUNKTION in DCM - unit (str): Unit of the parameter, started by EINHEIT_W in DCM - value (float/int): Value of the parameter, started by WERT in DCM - text (str): Alternative text-value, started by TEXT in DCM - comment (str): Block comment - """ - - attrs: dict = field(default_factory=_attrs_init) diff --git a/src/dcmReader/dcm_parameter_block.py b/src/dcmReader/dcm_parameter_block.py deleted file mode 100644 index fd031bb..0000000 --- a/src/dcmReader/dcm_parameter_block.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -Definition of DCM parameter block -""" -from __future__ import annotations - -from dataclasses import dataclass, field - -from dcmReader.utils import _DcmBase - - -def _attrs_init() -> dict: - return { - "description": "", - "display_name": "", - "variants": {}, - "function": "", - "units_x": "", - "units_y": "", - "units": "", - } - - -@dataclass -class DcmParameterBlock(_DcmBase): - """Definition of a parameter - - Attributes: - name (str): Name of the parameter - description (str): Description of the parameter, started by LANGNAME in DCM - display_name (str): Parameter name according asam-2mc, started by DISPLAYNAME in DCM - variants (dict): Variants for the parameter, started by VAR in DCM - function (str): Name of the assigned function, started by FUNKTION in DCM - unit (str): Unit of the parameter, started by EINHEIT_W in DCM - value (float/int): Value of the parameter, started by WERT in DCM - text (str): Alternative text-value, started by TEXT in DCM - comment (str): Block comment - """ - - attrs: dict = field(default_factory=_attrs_init) - - -# @dataclass -# class DcmParameterBlock: -# """Definition of a block parameter - -# Attributes: -# name (str): Name of the block parameter -# description (str): Description of the block parameter, started by LANGNAME in DCM -# display_name (str): Block parameter name according asam-2mc, started by DISPLAYNAME in DCM -# variants (dict): Variants for the block parameter, started by VAR in DCM -# function (str): Name of the assigned function, started by FUNKTION in DCM -# unit (str): Unit of the block parameters, started by EINHEIT_W in DCM -# values (list): List of values of the block parameter, started by WERT in DCM -# x_dimension (int): Dimension in x direction of the block parameter -# y_dimension (int): Dimension in y direction of the block parameter -# comment (str): Block comment -# """ - -# def __init__(self, name): -# self.name = name -# self.values = [] -# self.description = None -# self.display_name = None -# self.variants = {} -# self.function = None -# self.unit = None -# self.x_dimension = 0 -# self.y_dimension = 0 -# self.comment = None - -# def __str__(self): -# value = f"FESTWERTEBLOCK {self.name} {self.x_dimension}" -# if self.y_dimension > 1: -# value += f" @ {self.y_dimension}\n" -# else: -# value += "\n" - -# if self.comment: -# for line in self.comment.splitlines(True): -# value += f"* {line}" -# if self.description: -# value += f' LANGNAME "{self.description}"\n' -# if self.function: -# value += f' FUNKTION "{self.function}"\n' -# if self.display_name: -# value += f" DISPLAYNAME {self.display_name}\n" -# if self.unit: -# value += f' EINHEIT_W "{self.unit}"\n' -# if self.values: -# for entry in self.values: -# value += f' WERT {" ".join([str(x) for x in entry])}\n' - -# for var_name, var_value in self.variants.items(): -# value += f" VAR {var_name}={var_value}\n" - -# value += "END" - -# return value - -# def __lt__(self, other): -# return self.function < other.function and self.description < other.description diff --git a/src/dcmReader/elements.py b/src/dcmReader/elements.py new file mode 100644 index 0000000..840c058 --- /dev/null +++ b/src/dcmReader/elements.py @@ -0,0 +1,190 @@ +""" +Definition of DCM characteristic line +""" +from __future__ import annotations + +from dataclasses import dataclass, field + +from dcmReader.utils import _DcmBase + + +def _attrs_init() -> dict: + return {} + # return { + # "description": "", + # "display_name": "", + # "variants": {}, + # "function": "", + # "units_x": "", + # "units_y": "", + # "units": "", + # } + + +@dataclass +class DcmFunction(_DcmBase): + """Definition of a function + + Attributes: + name (str): Name of the function + version (str): Version number of the function + description (str): Description of the function + """ + + attrs: dict = field(default_factory=_attrs_init) + + def _print_dcm_format(self) -> str: + version = self.attrs.get("version", "") + description = self.attrs.get("description", "") + return f'{self.block_type} {self.name} "{version}" "{description}"' + + +@dataclass +class DcmVariantCoding(_DcmBase): + """Definition of a function + + Attributes: + name (str): Name of the function + version (str): Version number of the function + description (str): Description of the function + """ + + attrs: dict = field(default_factory=_attrs_init) + + def _print_dcm_format(self) -> str: + raise NotImplementedError + + +@dataclass +class DcmModuleHeader(_DcmBase): + """Definition of a function + + Attributes: + name (str): Name of the function + version (str): Version number of the function + description (str): Description of the function + """ + + attrs: dict = field(default_factory=_attrs_init) + + def _print_dcm_format(self) -> str: + raise NotImplementedError + + +@dataclass +class DcmParameter(_DcmBase): + """Definition of a parameter + + Attributes: + name (str): Name of the parameter + description (str): Description of the parameter, started by LANGNAME in DCM + display_name (str): Parameter name according asam-2mc, started by DISPLAYNAME in DCM + variants (dict): Variants for the parameter, started by VAR in DCM + function (str): Name of the assigned function, started by FUNKTION in DCM + unit (str): Unit of the parameter, started by EINHEIT_W in DCM + value (float/int): Value of the parameter, started by WERT in DCM + text (str): Alternative text-value, started by TEXT in DCM + comment (str): Block comment + """ + + attrs: dict = field(default_factory=_attrs_init) + + +@dataclass +class DcmParameterBlock(_DcmBase): + """Definition of a parameter + + Attributes: + name (str): Name of the parameter + description (str): Description of the parameter, started by LANGNAME in DCM + display_name (str): Parameter name according asam-2mc, started by DISPLAYNAME in DCM + variants (dict): Variants for the parameter, started by VAR in DCM + function (str): Name of the assigned function, started by FUNKTION in DCM + unit (str): Unit of the parameter, started by EINHEIT_W in DCM + value (float/int): Value of the parameter, started by WERT in DCM + text (str): Alternative text-value, started by TEXT in DCM + comment (str): Block comment + """ + + attrs: dict = field(default_factory=_attrs_init) + + +@dataclass +class DcmCharacteristicLine(_DcmBase): + """Definition of a characteristic line + + Attributes: + name (str): Name of the characteristic line + description (str): Description of the characteristic line, started by LANGNAME in DCM + display_name (str): Characteristic line name according asam-2mc, started by DISPLAYNAME in DCM + variants (dict): Variants for the characteristic line, started by VAR in DCM + function (str): Name of the assigned function, started by FUNKTION in DCM + unit_x (str): Unit of the x axis values, started by EINHEIT_X in DCM + unit_values (str): Unit of the values, started by EINHEIT_W in DCM + values (dict): Dict of values of the parameter, KEYs are retrieved from ST/X, + values are retrieved from WERT + x_dimension (int): Dimension in x direction of the parameter block + x_mapping (str): Mapping of the x axis to a distribution, if available as a comment in DCM + comment (str): Block comment + """ + + attrs: dict = field(default_factory=_attrs_init) + + +class DcmFixedCharacteristicLine(DcmCharacteristicLine): + """Definition of a fixed characteristic line, derived from characteristic line""" + + +class DcmGroupCharacteristicLine(DcmCharacteristicLine): + """Definition of a group characteristic line, derived from characteristic line""" + + +@dataclass +class DcmCharacteristicMap(_DcmBase): + """Definition of a characteristic map + + Attributes: + name (str): Name of the characteristic map + description (str): Description of the characteristic map, started by LANGNAME in DCM + display_name (str): Characteristic map name according asam-2mc, started by DISPLAYNAME in DCM + variants (dict): Variants for the characteristic map, started by VAR in DCM + function (str): Name of the assigned function, started by FUNKTION in DCM + unit_x (str): Unit of the x axis values, started by EINHEIT_X in DCM + unit_y (str): Unit of the y axis values, started by EINHEIT_Y in DCM + unit_values (str): Unit of the values, started by EINHEIT_W in DCM + values (dict): 2D Dict of values of the parameter + The inner dict contains the values from ST/X as keys and the + values retrieved from WERT as values. The keys of the outer dict + contains the values from ST/Y. + x_dimension (int): Dimension in x direction of the characteristic maps + y_dimension (int): Dimension in y direction of the characteristic maps + """ + + attrs: dict = field(default_factory=_attrs_init) + + +class DcmFixedCharacteristicMap(DcmCharacteristicMap): + """Definition of a fixed characteristic map, derived from characteristic map""" + + +class DcmGroupCharacteristicMap(DcmCharacteristicMap): + """Definition of a group characteristic map, derived from characteristic map""" + + +@dataclass +class DcmDistribution(_DcmBase): + """Definition of a distribution + + Attributes: + name (str): Name of the distribution + description (str): Description of the distribution, started by LANGNAME in DCM + display_name (str): Distribution name according asam-2mc, started by DISPLAYNAME in DCM + variants (dict): Variants for the distribution, started by VAR in DCM + function (str): Name of the assigned function, started by FUNKTION in DCM + unit_x (str): Unit of the x axis values, started by EINHEIT_X in DCM + values (list): List of values of the distribution, values are retrieved from WERT + x_dimension (int): Dimension in x direction of the distribution + comment (str): Block comment + """ + + attrs: dict = field(default_factory=_attrs_init) From 5ac4600aff8983736b6b84773efdf951c53f4199 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sat, 28 Jan 2023 08:45:45 +0100 Subject: [PATCH 20/28] Don't use os.linesep it's different and messes with tests. --- src/dcmReader/dcm_reader.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 1180635..1232f29 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -27,7 +27,7 @@ DcmGroupCharacteristicMap, DcmDistribution, ) -from dcmReader.utils import _SETTINGS +from dcmReader.utils import _COMMENT_QUALIFIER, _SETTINGS if TYPE_CHECKING: from io import TextIOWrapper @@ -39,8 +39,6 @@ logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.DEBUG) logger = logging.getLogger(__name__) -comment_qualifier = ("!", "*", ".") - @dataclass class DcmReader: @@ -92,7 +90,7 @@ def _parse_comment(self, line: str, *, element: T_Element, **kwargs) -> None: element.attrs[p["parse_key"]] = parsed_values else: cmnt_prev = element.attrs.get("comment", "") - element.attrs["comment"] = f"{cmnt_prev}{line_no_cq}{os.linesep}" + element.attrs["comment"] = f"{cmnt_prev}{line_no_cq}\n" def _parse_string(self, line: str, **kwargs) -> str: """Parses a text field @@ -284,9 +282,9 @@ def read(self, file) -> None: line = line.strip() # Check if line is comment - if line.startswith(comment_qualifier): + if line.startswith(_COMMENT_QUALIFIER): if not file_header_finished: - self.attrs["file_header"] = self.attrs.get("file_header", "") + line[1:].strip() + os.linesep + self.attrs["file_header"] = f"{self.attrs.get('file_header', '')}{line[1:].strip()}\n" continue # At this point first comment block passed From 204d929e5e7996c5984fdfdbf711a4dc0d6648f0 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sat, 28 Jan 2023 09:00:19 +0100 Subject: [PATCH 21/28] Rename Tests to test_dcmreader so pytest finds the file --- tests/{Tests.py => test_dcmreader.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{Tests.py => test_dcmreader.py} (100%) diff --git a/tests/Tests.py b/tests/test_dcmreader.py similarity index 100% rename from tests/Tests.py rename to tests/test_dcmreader.py From 97c84c902fe31533451c7f5d5cae59335914ebce Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sat, 28 Jan 2023 10:29:57 +0100 Subject: [PATCH 22/28] use join for multiple comments, removes trailing \n --- src/dcmReader/dcm_reader.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 1232f29..acb1f70 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -76,12 +76,12 @@ def _parse_coord_y(self, line: str, *, coord_y: list[float], **kwargs) -> None: def _parse_comment(self, line: str, *, element: T_Element, **kwargs) -> None: # TODO: Should the comment remember which row? So it can be reprinted there? - cq, line_no_cq = line[0], line[1:].strip() + line_no_cq = line[1:].strip() k = line_no_cq.split(None, 1)[0] p = self.parser_methods.get(k, None) if p is not None: - # The comment has known keyword, parse it accordingly: + # The comment has a known keyword, parse it accordingly: parsed_values = p["parse_method"](self)(line_no_cq, attrs=element.attrs, **kwargs) # Optionally store in attrs, otherwise assume it's @@ -90,7 +90,8 @@ def _parse_comment(self, line: str, *, element: T_Element, **kwargs) -> None: element.attrs[p["parse_key"]] = parsed_values else: cmnt_prev = element.attrs.get("comment", "") - element.attrs["comment"] = f"{cmnt_prev}{line_no_cq}\n" + cmnts = [cmnt_prev, line_no_cq] if cmnt_prev else [line_no_cq] + element.attrs["comment"] = "\n".join(cmnts) def _parse_string(self, line: str, **kwargs) -> str: """Parses a text field From e6dca19f26c58b6452e6322fdd573ffa457eeb8c Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sat, 28 Jan 2023 10:30:33 +0100 Subject: [PATCH 23/28] y/x_mapping to name_y/x --- src/dcmReader/utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py index 960f2c4..297254a 100644 --- a/src/dcmReader/utils.py +++ b/src/dcmReader/utils.py @@ -174,19 +174,19 @@ def _print_values(key: str, value: ArrayLike, fmt: T_Fmt = fmt_base, n: int = 6, "SSTX": { # From comments: "key_ger": "SSTX", - "key_eng": "x_mapping", + "key_eng": "name_x", "print_key": "*SSTY", "print_format": lambda x: f"{x}", - "parse_key": "x_mapping", + "parse_key": "name_x", "parse_method": lambda self: self._parse_string, }, "SSTY": { # From comments: "key_ger": "SSTY", - "key_eng": "y_mapping", + "key_eng": "name_x", "print_key": "*SSTY", "print_format": lambda x: f"{x}", - "parse_key": "y_mapping", + "parse_key": "name_x", "parse_method": lambda self: self._parse_string, }, "WERT": { From 2958e9c377e0b6fe7837885566abe591b16aa8bb Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sat, 28 Jan 2023 11:01:29 +0100 Subject: [PATCH 24/28] block_type to element_syntax, matches better with the technical notes --- src/dcmReader/dcm_reader.py | 16 ++++++++-------- src/dcmReader/elements.py | 2 +- src/dcmReader/utils.py | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index acb1f70..69b4115 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -162,8 +162,8 @@ def _convert_value(self, value: str) -> float: raise ValueError(f"Cannot convert {value} from string to number.") from err def _parse_elements(self, DcmElement: type[T_Element], line_intro: str, dcm_file: TextIOWrapper) -> T_Element: - block_type, name, *shape_rev = line_intro.split() - element = DcmElement(name=name, block_type=block_type) + element_syntax, name, *shape_rev = line_intro.split() + element = DcmElement(name=name, element_syntax=element_syntax) # Reverse the order of x and y to match numpy: shape_rev.reverse() @@ -178,13 +178,13 @@ def _parse_elements(self, DcmElement: type[T_Element], line_intro: str, dcm_file keyword = line.split(None, 1)[0] if keyword.startswith("END"): - if block_type == "STUETZSTELLENVERTEILUNG": + if element_syntax == "STUETZSTELLENVERTEILUNG": element.values = np.asarray(coord_x) - element.attrs["x_mapping"] = element.name + element.attrs[_SETTINGS["SSTX"]["key_eng"]] = element.name # Handle coords and dims: xy = ("x", "y") - k_axis = tuple(f"{v}_mapping" for v in xy) + k_axis = tuple(_SETTINGS[f"SST{v.upper()}"]["key_eng"] for v in xy) crds_rev = (coord_x, coord_y) dims_rev = [] coords_rev = [] @@ -223,7 +223,7 @@ def _parse_elements(self, DcmElement: type[T_Element], line_intro: str, dcm_file def _parse_elements_funktionen( self, DcmElement: type[T_Element], line_intro: str, dcm_file: TextIOWrapper ) -> list[T_Element]: - # block_type = line_intro.strip() + # element_syntax = line_intro.strip() elements = [] while True: @@ -236,14 +236,14 @@ def _parse_elements_funktionen( break else: function_match = re.search(r"FKT (.*?)(?: \"(.*?)?\"(?: \"(.*?)?\")?)?$", line.strip()) - block_type_short = "FKT" + element_syntax_short = "FKT" fm: dict[str, str] = {"name": "", "version": "", "description": ""} for i, (k, v) in enumerate(fm.items()): if function_match is not None: fm[k] = function_match.group(i + 1) - element = DcmElement(name=fm["name"], block_type=block_type_short) + element = DcmElement(name=fm["name"], element_syntax=element_syntax_short) element.attrs["version"] = fm["version"] element.attrs["description"] = fm["description"] element.attrs["_function"] = "FUNKTIONEN" diff --git a/src/dcmReader/elements.py b/src/dcmReader/elements.py index 840c058..8cd94cd 100644 --- a/src/dcmReader/elements.py +++ b/src/dcmReader/elements.py @@ -36,7 +36,7 @@ class DcmFunction(_DcmBase): def _print_dcm_format(self) -> str: version = self.attrs.get("version", "") description = self.attrs.get("description", "") - return f'{self.block_type} {self.name} "{version}" "{description}"' + return f'{self.element_syntax} {self.name} "{version}" "{description}"' @dataclass diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py index 297254a..f470b92 100644 --- a/src/dcmReader/utils.py +++ b/src/dcmReader/utils.py @@ -183,10 +183,10 @@ def _print_values(key: str, value: ArrayLike, fmt: T_Fmt = fmt_base, n: int = 6, "SSTY": { # From comments: "key_ger": "SSTY", - "key_eng": "name_x", + "key_eng": "name_y", "print_key": "*SSTY", "print_format": lambda x: f"{x}", - "parse_key": "name_x", + "parse_key": "name_y", "parse_method": lambda self: self._parse_string, }, "WERT": { @@ -237,7 +237,7 @@ class _DcmBase(ShapeRelatedMixin): coords: tuple[np.ndarray, ...] = field(default_factory=tuple) dims: tuple[str, ...] = field(default_factory=tuple) attrs: dict = field(default_factory=dict) - block_type: str = "" + element_syntax: str = "" def __lt__(self, other): self_function = self.attrs.get("function", "") @@ -268,13 +268,13 @@ def _print_dcm_format(self) -> str: ndim = self.ndim shape_rev: list[int | str] = list(reversed(self.shape)) - if self.block_type == "FESTWERTEBLOCK" and ndim == 2: + if self.element_syntax == "FESTWERTEBLOCK" and ndim == 2: shape_rev.insert(1, "@") coords_rev = list(reversed(self.coords)) ncoords = len(coords_rev) # Header: - value = f"{self.block_type} {self.name} {_to_str(shape_rev)}\n" + value = f"{self.element_syntax} {self.name} {_to_str(shape_rev)}\n" # Attributes printed before the values: for k in _SETTINGS.keys(): From 142c60376c89203be4eec5b2e933cf1e3f93180d Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sat, 28 Jan 2023 11:01:44 +0100 Subject: [PATCH 25/28] Update tests --- tests/test_dcmreader.py | 226 +++++++++++++++++++--------------------- 1 file changed, 105 insertions(+), 121 deletions(-) diff --git a/tests/test_dcmreader.py b/tests/test_dcmreader.py index 79fbc0e..0d5b410 100644 --- a/tests/test_dcmreader.py +++ b/tests/test_dcmreader.py @@ -44,13 +44,13 @@ def test_valueParameter(self): valueParameter = dcm.get_parameters()[0] self.assertEqual("valueParameter", valueParameter.name) - self.assertEqual("Sample value parameter", valueParameter.description) - self.assertEqual("ParameterFunction", valueParameter.function) - self.assertEqual("°C", valueParameter.unit) - self.assertEqual(25.0, valueParameter.value) - self.assertEqual(27.5, valueParameter.variants["VariantA"]) + self.assertEqual("Sample value parameter", valueParameter.attrs["description"]) + self.assertEqual("ParameterFunction", valueParameter.attrs["function"]) + self.assertEqual("°C", valueParameter.attrs["units"]) + self.assertEqual(25.0, valueParameter.values) + self.assertEqual(27.5, valueParameter.attrs["variants"]["VariantA"]) self.assertEqual(None, valueParameter.text) - self.assertEqual("Sample comment\nSecond comment line\n", valueParameter.comment) + self.assertEqual("Sample comment\nSecond comment line", valueParameter.attrs["comment"]) def test_textParameter(self): dcm = DcmReader() @@ -60,11 +60,11 @@ def test_textParameter(self): valueParameter = dcm.get_parameters()[1] self.assertEqual("textParameter", valueParameter.name) - self.assertEqual("Sample text parameter", valueParameter.description) - self.assertEqual("ParameterFunction", valueParameter.function) - self.assertEqual("-", valueParameter.unit) - self.assertEqual(None, valueParameter.value) - self.assertEqual("ParameterB", valueParameter.variants["VariantA"]) + self.assertEqual("Sample text parameter", valueParameter.attrs["description"]) + self.assertEqual("ParameterFunction", valueParameter.attrs["function"]) + self.assertEqual("-", valueParameter.attrs["units"]) + self.assertEqual(None, valueParameter.values) + self.assertEqual("ParameterB", valueParameter.attrs["variants"]["VariantA"]) self.assertEqual("ParameterA", valueParameter.text) @@ -78,26 +78,26 @@ def test_blockParameter1D(self): blockParameterWritten = dcmWritten.get_block_parameters()[0] self.assertEqual("blockParameter1D", blockParameter.name) - self.assertEqual("Sample block parameters", blockParameter.description) - self.assertEqual("BlockParameterFunction", blockParameter.function) - self.assertEqual("BlockParameterDisplayname", blockParameter.display_name) - self.assertEqual("°C", blockParameter.unit) + self.assertEqual("Sample block parameters", blockParameter.attrs["description"]) + self.assertEqual("BlockParameterFunction", blockParameter.attrs["function"]) + self.assertEqual("BlockParameterDisplayname", blockParameter.attrs["display_name"]) + self.assertEqual("°C", blockParameter.attrs["units"]) self.assertEqual(0.75, blockParameter.values[0][0]) self.assertEqual(-0.25, blockParameter.values[0][1]) self.assertEqual(0.5, blockParameter.values[0][2]) self.assertEqual(1.5, blockParameter.values[0][3]) - self.assertEqual("Sample comment\n", blockParameter.comment) + self.assertEqual("Sample comment", blockParameter.attrs["comment"]) self.assertEqual("blockParameter1D", blockParameterWritten.name) - self.assertEqual("Sample block parameters", blockParameterWritten.description) - self.assertEqual("BlockParameterFunction", blockParameterWritten.function) - self.assertEqual("BlockParameterDisplayname", blockParameterWritten.display_name) - self.assertEqual("°C", blockParameterWritten.unit) + self.assertEqual("Sample block parameters", blockParameterWritten.attrs["description"]) + self.assertEqual("BlockParameterFunction", blockParameterWritten.attrs["function"]) + self.assertEqual("BlockParameterDisplayname", blockParameterWritten.attrs["display_name"]) + self.assertEqual("°C", blockParameterWritten.attrs["units"]) self.assertEqual(0.75, blockParameterWritten.values[0][0]) self.assertEqual(-0.25, blockParameterWritten.values[0][1]) self.assertEqual(0.5, blockParameterWritten.values[0][2]) self.assertEqual(1.5, blockParameterWritten.values[0][3]) - self.assertEqual("Sample comment\n", blockParameterWritten.comment) + self.assertEqual("Sample comment", blockParameterWritten.attrs["comment"]) def test_blockParameter2D(self): dcm = DcmReader() @@ -108,10 +108,10 @@ def test_blockParameter2D(self): blockParameterWritten = dcmWritten.get_block_parameters()[1] self.assertEqual("blockParameter2D", blockParameter.name) - self.assertEqual("Sample block parameters", blockParameter.description) - self.assertEqual("BlockParameterFunction", blockParameter.function) - self.assertEqual("BlockParameterDisplayname", blockParameter.display_name) - self.assertEqual("°C", blockParameter.unit) + self.assertEqual("Sample block parameters", blockParameter.attrs["description"]) + self.assertEqual("BlockParameterFunction", blockParameter.attrs["function"]) + self.assertEqual("BlockParameterDisplayname", blockParameter.attrs["display_name"]) + self.assertEqual("°C", blockParameter.attrs["units"]) self.assertEqual(0.75, blockParameter.values[0][0]) self.assertEqual(-0.25, blockParameter.values[0][1]) self.assertEqual(0.5, blockParameter.values[0][2]) @@ -122,10 +122,10 @@ def test_blockParameter2D(self): self.assertEqual(11.5, blockParameter.values[1][3]) self.assertEqual("blockParameter2D", blockParameterWritten.name) - self.assertEqual("Sample block parameters", blockParameterWritten.description) - self.assertEqual("BlockParameterFunction", blockParameterWritten.function) - self.assertEqual("BlockParameterDisplayname", blockParameterWritten.display_name) - self.assertEqual("°C", blockParameterWritten.unit) + self.assertEqual("Sample block parameters", blockParameterWritten.attrs["description"]) + self.assertEqual("BlockParameterFunction", blockParameterWritten.attrs["function"]) + self.assertEqual("BlockParameterDisplayname", blockParameterWritten.attrs["display_name"]) + self.assertEqual("°C", blockParameterWritten.attrs["units"]) self.assertEqual(0.75, blockParameterWritten.values[0][0]) self.assertEqual(-0.25, blockParameterWritten.values[0][1]) self.assertEqual(0.5, blockParameterWritten.values[0][2]) @@ -148,11 +148,11 @@ def test_characteristicLine(self): self.assertEqual(1, len(dcm.get_characteristic_lines())) self.assertEqual("characteristicLine", characteristic.name) - self.assertEqual("Sample characteristic line", characteristic.description) - self.assertEqual("CharacteristicLineFunction", characteristic.function) - self.assertEqual("CharacteristicLineDisplayname", characteristic.display_name) + self.assertEqual("Sample characteristic line", characteristic.attrs["description"]) + self.assertEqual("CharacteristicLineFunction", characteristic.attrs["function"]) + self.assertEqual("CharacteristicLineDisplayname", characteristic.attrs["display_name"]) self.assertEqual("°", characteristic.unit_values) - self.assertEqual("s", characteristic.unit_x) + self.assertEqual("s", characteristic.attrs["units_x"]) self.assertEqual(0.0, characteristic.values[0.0]) self.assertEqual(80.0, characteristic.values[1.0]) self.assertEqual(120.0, characteristic.values[2.0]) @@ -162,16 +162,16 @@ def test_characteristicLine(self): self.assertEqual(300.0, characteristic.values[6.0]) self.assertEqual(340.0, characteristic.values[7.0]) self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) - self.assertEqual("Sample comment\n", characteristic.comment) + self.assertEqual("Sample comment", characteristic.attrs["comment"]) self.assertEqual(1, len(dcmWritten.get_characteristic_lines())) self.assertEqual("characteristicLine", characteristicWritten.name) - self.assertEqual("Sample characteristic line", characteristicWritten.description) - self.assertEqual("CharacteristicLineFunction", characteristicWritten.function) - self.assertEqual("CharacteristicLineDisplayname", characteristicWritten.display_name) + self.assertEqual("Sample characteristic line", characteristicWritten.attrs["description"]) + self.assertEqual("CharacteristicLineFunction", characteristicWritten.attrs["function"]) + self.assertEqual("CharacteristicLineDisplayname", characteristicWritten.attrs["display_name"]) self.assertEqual("°", characteristicWritten.unit_values) - self.assertEqual("s", characteristicWritten.unit_x) + self.assertEqual("s", characteristicWritten.attrs["units_x"]) self.assertEqual(0.0, characteristicWritten.values[0.0]) self.assertEqual(80.0, characteristicWritten.values[1.0]) self.assertEqual(120.0, characteristicWritten.values[2.0]) @@ -181,7 +181,7 @@ def test_characteristicLine(self): self.assertEqual(300.0, characteristicWritten.values[6.0]) self.assertEqual(340.0, characteristicWritten.values[7.0]) self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) - self.assertEqual("Sample comment\n", characteristicWritten.comment) + self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) def test_fixedCharacteristicLine(self): dcm = DcmReader() @@ -194,13 +194,11 @@ def test_fixedCharacteristicLine(self): self.assertEqual(1, len(dcm.get_fixed_characteristic_lines())) self.assertEqual("fixedCharacteristicLine", characteristic.name) - self.assertEqual("Sample fixed characteristic line", characteristic.description) - self.assertEqual("FixedCharacteristicLineFunction", characteristic.function) - self.assertEqual( - "FixedCharacteristicLineDisplayname", characteristic.display_name - ) + self.assertEqual("Sample fixed characteristic line", characteristic.attrs["description"]) + self.assertEqual("FixedCharacteristicLineFunction", characteristic.attrs["function"]) + self.assertEqual("FixedCharacteristicLineDisplayname", characteristic.attrs["display_name"]) self.assertEqual("°", characteristic.unit_values) - self.assertEqual("s", characteristic.unit_x) + self.assertEqual("s", characteristic.attrs["units_x"]) self.assertEqual(45.0, characteristic.values[0.0]) self.assertEqual(90.0, characteristic.values[1.0]) self.assertEqual(135.0, characteristic.values[2.0]) @@ -208,18 +206,16 @@ def test_fixedCharacteristicLine(self): self.assertEqual(225.0, characteristic.values[4.0]) self.assertEqual(270.0, characteristic.values[5.0]) self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) - self.assertEqual("Sample comment\n", characteristic.comment) + self.assertEqual("Sample comment", characteristic.attrs["comment"]) self.assertEqual(1, len(dcmWritten.get_fixed_characteristic_lines())) self.assertEqual("fixedCharacteristicLine", characteristicWritten.name) - self.assertEqual("Sample fixed characteristic line", characteristicWritten.description) - self.assertEqual("FixedCharacteristicLineFunction", characteristicWritten.function) - self.assertEqual( - "FixedCharacteristicLineDisplayname", characteristicWritten.display_name - ) + self.assertEqual("Sample fixed characteristic line", characteristicWritten.attrs["description"]) + self.assertEqual("FixedCharacteristicLineFunction", characteristicWritten.attrs["function"]) + self.assertEqual("FixedCharacteristicLineDisplayname", characteristicWritten.attrs["display_name"]) self.assertEqual("°", characteristicWritten.unit_values) - self.assertEqual("s", characteristicWritten.unit_x) + self.assertEqual("s", characteristicWritten.attrs["units_x"]) self.assertEqual(45.0, characteristicWritten.values[0.0]) self.assertEqual(90.0, characteristicWritten.values[1.0]) self.assertEqual(135.0, characteristicWritten.values[2.0]) @@ -227,7 +223,7 @@ def test_fixedCharacteristicLine(self): self.assertEqual(225.0, characteristicWritten.values[4.0]) self.assertEqual(270.0, characteristicWritten.values[5.0]) self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) - self.assertEqual("Sample comment\n", characteristicWritten.comment) + self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) def test_groupCharacteristicLine(self): dcm = DcmReader() @@ -240,34 +236,30 @@ def test_groupCharacteristicLine(self): self.assertEqual(1, len(dcm.get_group_characteristic_lines())) self.assertEqual("groupCharacteristicLine", characteristic.name) - self.assertEqual("Sample group characteristic line", characteristic.description) - self.assertEqual("GroupCharacteristicLineFunction", characteristic.function) - self.assertEqual( - "GroupCharacteristicLineDisplayname", characteristic.display_name - ) + self.assertEqual("Sample group characteristic line", characteristic.attrs["description"]) + self.assertEqual("GroupCharacteristicLineFunction", characteristic.attrs["function"]) + self.assertEqual("GroupCharacteristicLineDisplayname", characteristic.attrs["display_name"]) self.assertEqual("°", characteristic.unit_values) - self.assertEqual("s", characteristic.unit_x) + self.assertEqual("s", characteristic.attrs["units_x"]) self.assertEqual(-45.0, characteristic.values[1.0]) self.assertEqual(-90.0, characteristic.values[2.0]) self.assertEqual(-135.0, characteristic.values[3.0]) self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) - self.assertEqual("Sample comment\n", characteristic.comment) + self.assertEqual("Sample comment", characteristic.attrs["comment"]) self.assertEqual(1, len(dcmWritten.get_group_characteristic_lines())) self.assertEqual("groupCharacteristicLine", characteristicWritten.name) - self.assertEqual("Sample group characteristic line", characteristicWritten.description) - self.assertEqual("GroupCharacteristicLineFunction", characteristicWritten.function) - self.assertEqual( - "GroupCharacteristicLineDisplayname", characteristicWritten.display_name - ) + self.assertEqual("Sample group characteristic line", characteristicWritten.attrs["description"]) + self.assertEqual("GroupCharacteristicLineFunction", characteristicWritten.attrs["function"]) + self.assertEqual("GroupCharacteristicLineDisplayname", characteristicWritten.attrs["display_name"]) self.assertEqual("°", characteristicWritten.unit_values) - self.assertEqual("s", characteristicWritten.unit_x) + self.assertEqual("s", characteristicWritten.attrs["units_x"]) self.assertEqual(-45.0, characteristicWritten.values[1.0]) self.assertEqual(-90.0, characteristicWritten.values[2.0]) self.assertEqual(-135.0, characteristicWritten.values[3.0]) self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) - self.assertEqual("Sample comment\n", characteristicWritten.comment) + self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) class TestCharacteristicMaps(unittest.TestCase): @@ -282,12 +274,12 @@ def test_characteristicMap(self): self.assertEqual(1, len(dcm.get_characteristic_maps())) self.assertEqual("characteristicMap", characteristic.name) - self.assertEqual("Sample characteristic map", characteristic.description) - self.assertEqual("CharacteristicMapFunction", characteristic.function) - self.assertEqual("CharacteristicMapDisplayname", characteristic.display_name) + self.assertEqual("Sample characteristic map", characteristic.attrs["description"]) + self.assertEqual("CharacteristicMapFunction", characteristic.attrs["function"]) + self.assertEqual("CharacteristicMapDisplayname", characteristic.attrs["display_name"]) self.assertEqual("bar", characteristic.unit_values) - self.assertEqual("°C", characteristic.unit_x) - self.assertEqual("m/s", characteristic.unit_y) + self.assertEqual("°C", characteristic.attrs["units_x"]) + self.assertEqual("m/s", characteristic.attrs["units_y"]) self.assertEqual(0.0, characteristic.values[1.0][1.0]) self.assertEqual(0.4, characteristic.values[1.0][2.0]) self.assertEqual(0.8, characteristic.values[1.0][3.0]) @@ -302,17 +294,17 @@ def test_characteristicMap(self): self.assertEqual(4.0, characteristic.values[2.0][6.0]) self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) self.assertEqual("DISTRIBUTION Y", characteristic.y_mapping) - self.assertEqual("Sample comment\n", characteristic.comment) + self.assertEqual("Sample comment", characteristic.attrs["comment"]) self.assertEqual(1, len(dcmWritten.get_characteristic_maps())) self.assertEqual("characteristicMap", characteristicWritten.name) - self.assertEqual("Sample characteristic map", characteristicWritten.description) - self.assertEqual("CharacteristicMapFunction", characteristicWritten.function) - self.assertEqual("CharacteristicMapDisplayname", characteristicWritten.display_name) + self.assertEqual("Sample characteristic map", characteristicWritten.attrs["description"]) + self.assertEqual("CharacteristicMapFunction", characteristicWritten.attrs["function"]) + self.assertEqual("CharacteristicMapDisplayname", characteristicWritten.attrs["display_name"]) self.assertEqual("bar", characteristicWritten.unit_values) - self.assertEqual("°C", characteristicWritten.unit_x) - self.assertEqual("m/s", characteristicWritten.unit_y) + self.assertEqual("°C", characteristicWritten.attrs["units_x"]) + self.assertEqual("m/s", characteristicWritten.attrs["units_y"]) self.assertEqual(0.0, characteristicWritten.values[1.0][1.0]) self.assertEqual(0.4, characteristicWritten.values[1.0][2.0]) self.assertEqual(0.8, characteristicWritten.values[1.0][3.0]) @@ -327,7 +319,7 @@ def test_characteristicMap(self): self.assertEqual(4.0, characteristicWritten.values[2.0][6.0]) self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) self.assertEqual("DISTRIBUTION Y", characteristicWritten.y_mapping) - self.assertEqual("Sample comment\n", characteristicWritten.comment) + self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) def test_fixedCharacteristicMap(self): dcm = DcmReader() @@ -340,14 +332,12 @@ def test_fixedCharacteristicMap(self): self.assertEqual(1, len(dcm.get_fixed_characteristic_maps())) self.assertEqual("fixedCharacteristicMap", characteristic.name) - self.assertEqual("Sample fixed characteristic map", characteristic.description) - self.assertEqual("FixedCharacteristicMapFunction", characteristic.function) - self.assertEqual( - "FixedCharacteristicMapDisplayname", characteristic.display_name - ) + self.assertEqual("Sample fixed characteristic map", characteristic.attrs["description"]) + self.assertEqual("FixedCharacteristicMapFunction", characteristic.attrs["function"]) + self.assertEqual("FixedCharacteristicMapDisplayname", characteristic.attrs["display_name"]) self.assertEqual("bar", characteristic.unit_values) - self.assertEqual("°C", characteristic.unit_x) - self.assertEqual("m/s", characteristic.unit_y) + self.assertEqual("°C", characteristic.attrs["units_x"]) + self.assertEqual("m/s", characteristic.attrs["units_y"]) self.assertEqual(0.0, characteristic.values[0.0][1.0]) self.assertEqual(0.4, characteristic.values[0.0][2.0]) self.assertEqual(0.8, characteristic.values[0.0][3.0]) @@ -362,19 +352,17 @@ def test_fixedCharacteristicMap(self): self.assertEqual(4.0, characteristic.values[1.0][6.0]) self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) self.assertEqual("DISTRIBUTION Y", characteristic.y_mapping) - self.assertEqual("Sample comment\n", characteristic.comment) + self.assertEqual("Sample comment", characteristic.attrs["comment"]) self.assertEqual(1, len(dcmWritten.get_fixed_characteristic_maps())) self.assertEqual("fixedCharacteristicMap", characteristicWritten.name) - self.assertEqual("Sample fixed characteristic map", characteristicWritten.description) - self.assertEqual("FixedCharacteristicMapFunction", characteristicWritten.function) - self.assertEqual( - "FixedCharacteristicMapDisplayname", characteristicWritten.display_name - ) + self.assertEqual("Sample fixed characteristic map", characteristicWritten.attrs["description"]) + self.assertEqual("FixedCharacteristicMapFunction", characteristicWritten.attrs["function"]) + self.assertEqual("FixedCharacteristicMapDisplayname", characteristicWritten.attrs["display_name"]) self.assertEqual("bar", characteristicWritten.unit_values) - self.assertEqual("°C", characteristicWritten.unit_x) - self.assertEqual("m/s", characteristicWritten.unit_y) + self.assertEqual("°C", characteristicWritten.attrs["units_x"]) + self.assertEqual("m/s", characteristicWritten.attrs["units_y"]) self.assertEqual(0.0, characteristicWritten.values[0.0][1.0]) self.assertEqual(0.4, characteristicWritten.values[0.0][2.0]) self.assertEqual(0.8, characteristicWritten.values[0.0][3.0]) @@ -389,7 +377,7 @@ def test_fixedCharacteristicMap(self): self.assertEqual(4.0, characteristicWritten.values[1.0][6.0]) self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) self.assertEqual("DISTRIBUTION Y", characteristicWritten.y_mapping) - self.assertEqual("Sample comment\n", characteristicWritten.comment) + self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) def test_groupCharacteristicMap(self): dcm = DcmReader() @@ -402,14 +390,12 @@ def test_groupCharacteristicMap(self): self.assertEqual(1, len(dcm.get_group_characteristic_maps())) self.assertEqual("groupCharacteristicMap", characteristic.name) - self.assertEqual("Sample group characteristic map", characteristic.description) - self.assertEqual("GroupCharacteristicMapFunction", characteristic.function) - self.assertEqual( - "GroupCharacteristicMapDisplayname", characteristic.display_name - ) + self.assertEqual("Sample group characteristic map", characteristic.attrs["description"]) + self.assertEqual("GroupCharacteristicMapFunction", characteristic.attrs["function"]) + self.assertEqual("GroupCharacteristicMapDisplayname", characteristic.attrs["display_name"]) self.assertEqual("bar", characteristic.unit_values) - self.assertEqual("°C", characteristic.unit_x) - self.assertEqual("m/s", characteristic.unit_y) + self.assertEqual("°C", characteristic.attrs["units_x"]) + self.assertEqual("m/s", characteristic.attrs["units_y"]) self.assertEqual(1.0, characteristic.values[1.0][1.0]) self.assertEqual(2.0, characteristic.values[1.0][2.0]) self.assertEqual(3.0, characteristic.values[1.0][3.0]) @@ -430,19 +416,17 @@ def test_groupCharacteristicMap(self): self.assertEqual(9.0, characteristic.values[3.0][6.0]) self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) self.assertEqual("DISTRIBUTION Y", characteristic.y_mapping) - self.assertEqual("Sample comment\n", characteristic.comment) + self.assertEqual("Sample comment", characteristic.attrs["comment"]) self.assertEqual(1, len(dcmWritten.get_group_characteristic_maps())) self.assertEqual("groupCharacteristicMap", characteristicWritten.name) - self.assertEqual("Sample group characteristic map", characteristicWritten.description) - self.assertEqual("GroupCharacteristicMapFunction", characteristicWritten.function) - self.assertEqual( - "GroupCharacteristicMapDisplayname", characteristicWritten.display_name - ) + self.assertEqual("Sample group characteristic map", characteristicWritten.attrs["description"]) + self.assertEqual("GroupCharacteristicMapFunction", characteristicWritten.attrs["function"]) + self.assertEqual("GroupCharacteristicMapDisplayname", characteristicWritten.attrs["display_name"]) self.assertEqual("bar", characteristicWritten.unit_values) - self.assertEqual("°C", characteristicWritten.unit_x) - self.assertEqual("m/s", characteristicWritten.unit_y) + self.assertEqual("°C", characteristicWritten.attrs["units_x"]) + self.assertEqual("m/s", characteristicWritten.attrs["units_y"]) self.assertEqual(1.0, characteristicWritten.values[1.0][1.0]) self.assertEqual(2.0, characteristicWritten.values[1.0][2.0]) self.assertEqual(3.0, characteristicWritten.values[1.0][3.0]) @@ -463,7 +447,7 @@ def test_groupCharacteristicMap(self): self.assertEqual(9.0, characteristicWritten.values[3.0][6.0]) self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) self.assertEqual("DISTRIBUTION Y", characteristicWritten.y_mapping) - self.assertEqual("Sample comment\n", characteristicWritten.comment) + self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) class TestDistribution(unittest.TestCase): @@ -478,26 +462,26 @@ def test_distribution(self): self.assertEqual(1, len(dcm.get_distributions())) self.assertEqual("distrib", distribution.name) - self.assertEqual("Sample distribution", distribution.description) - self.assertEqual("DistributionFunction", distribution.function) - self.assertEqual("DistributionDisplayname", distribution.display_name) - self.assertEqual("mm", distribution.unit_x) + self.assertEqual("Sample distribution", distribution.attrs["description"]) + self.assertEqual("DistributionFunction", distribution.attrs["function"]) + self.assertEqual("DistributionDisplayname", distribution.attrs["display_name"]) + self.assertEqual("mm", distribution.attrs["units_x"]) self.assertEqual(1.0, distribution.values[0]) self.assertEqual(2.0, distribution.values[1]) self.assertEqual(3.0, distribution.values[2]) - self.assertEqual("SST\n", distribution.comment) + self.assertEqual("SST", distribution.attrs["comment"]) self.assertEqual(1, len(dcmWritten.get_distributions())) self.assertEqual("distrib", distributionWritten.name) - self.assertEqual("Sample distribution", distributionWritten.description) - self.assertEqual("DistributionFunction", distributionWritten.function) - self.assertEqual("DistributionDisplayname", distributionWritten.display_name) - self.assertEqual("mm", distributionWritten.unit_x) + self.assertEqual("Sample distribution", distributionWritten.attrs["description"]) + self.assertEqual("DistributionFunction", distributionWritten.attrs["function"]) + self.assertEqual("DistributionDisplayname", distributionWritten.attrs["display_name"]) + self.assertEqual("mm", distributionWritten.attrs["units_x"]) self.assertEqual(1.0, distributionWritten.values[0]) self.assertEqual(2.0, distributionWritten.values[1]) self.assertEqual(3.0, distributionWritten.values[2]) - self.assertEqual("SST\n", distributionWritten.comment) + self.assertEqual("SST", distributionWritten.attrs["comment"]) if __name__ == "__main__": From f240c8aeddca6b1f82b1a5a90afbe1f4dc96c48a Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Mon, 30 Jan 2023 22:16:51 +0100 Subject: [PATCH 26/28] Update dcm_reader.py --- src/dcmReader/dcm_reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 69b4115..0f8d11c 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -413,7 +413,7 @@ def __str__(self) -> str: output_string += "END\n\n" # Print rest of DCM objects - object_list: list[_DcmBase] = [] + object_list: list[T_Element] = [] object_list.extend(self._parameter_list) object_list.extend(self._block_parameter_list) object_list.extend(self._characteristic_line_list) From 3d407ca771656595b740c3f2ef095d9d025e162a Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 19 Mar 2023 17:57:54 +0100 Subject: [PATCH 27/28] Move to pytest and parametrize --- tests/test_dcmreader.py | 680 ++++++++++++++-------------------------- 1 file changed, 232 insertions(+), 448 deletions(-) diff --git a/tests/test_dcmreader.py b/tests/test_dcmreader.py index 0d5b410..25358b8 100644 --- a/tests/test_dcmreader.py +++ b/tests/test_dcmreader.py @@ -1,13 +1,17 @@ import os import sys import unittest +import pytest + +import numpy as np + +from dcmReader.dcm_reader import DcmReader + testdir = os.path.dirname(__file__) srcdir = "../src" sys.path.insert(0, os.path.abspath(os.path.join(testdir, srcdir))) -from dcmReader.dcm_reader import DcmReader - class TestWriteFile(unittest.TestCase): def test_fileWriting(self): @@ -36,453 +40,233 @@ def test_foundParameter(self): self.assertEqual(2, len(dcm.get_parameters())) self.assertEqual(2, len(dcmWritten.get_parameters())) - def test_valueParameter(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - valueParameter = dcm.get_parameters()[0] - - self.assertEqual("valueParameter", valueParameter.name) - self.assertEqual("Sample value parameter", valueParameter.attrs["description"]) - self.assertEqual("ParameterFunction", valueParameter.attrs["function"]) - self.assertEqual("°C", valueParameter.attrs["units"]) - self.assertEqual(25.0, valueParameter.values) - self.assertEqual(27.5, valueParameter.attrs["variants"]["VariantA"]) - self.assertEqual(None, valueParameter.text) - self.assertEqual("Sample comment\nSecond comment line", valueParameter.attrs["comment"]) - - def test_textParameter(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - valueParameter = dcm.get_parameters()[1] - - self.assertEqual("textParameter", valueParameter.name) - self.assertEqual("Sample text parameter", valueParameter.attrs["description"]) - self.assertEqual("ParameterFunction", valueParameter.attrs["function"]) - self.assertEqual("-", valueParameter.attrs["units"]) - self.assertEqual(None, valueParameter.values) - self.assertEqual("ParameterB", valueParameter.attrs["variants"]["VariantA"]) - self.assertEqual("ParameterA", valueParameter.text) - - -class TestParameterBlock(unittest.TestCase): - def test_blockParameter1D(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - blockParameter = dcm.get_block_parameters()[0] - blockParameterWritten = dcmWritten.get_block_parameters()[0] - - self.assertEqual("blockParameter1D", blockParameter.name) - self.assertEqual("Sample block parameters", blockParameter.attrs["description"]) - self.assertEqual("BlockParameterFunction", blockParameter.attrs["function"]) - self.assertEqual("BlockParameterDisplayname", blockParameter.attrs["display_name"]) - self.assertEqual("°C", blockParameter.attrs["units"]) - self.assertEqual(0.75, blockParameter.values[0][0]) - self.assertEqual(-0.25, blockParameter.values[0][1]) - self.assertEqual(0.5, blockParameter.values[0][2]) - self.assertEqual(1.5, blockParameter.values[0][3]) - self.assertEqual("Sample comment", blockParameter.attrs["comment"]) - - self.assertEqual("blockParameter1D", blockParameterWritten.name) - self.assertEqual("Sample block parameters", blockParameterWritten.attrs["description"]) - self.assertEqual("BlockParameterFunction", blockParameterWritten.attrs["function"]) - self.assertEqual("BlockParameterDisplayname", blockParameterWritten.attrs["display_name"]) - self.assertEqual("°C", blockParameterWritten.attrs["units"]) - self.assertEqual(0.75, blockParameterWritten.values[0][0]) - self.assertEqual(-0.25, blockParameterWritten.values[0][1]) - self.assertEqual(0.5, blockParameterWritten.values[0][2]) - self.assertEqual(1.5, blockParameterWritten.values[0][3]) - self.assertEqual("Sample comment", blockParameterWritten.attrs["comment"]) - - def test_blockParameter2D(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - blockParameter = dcm.get_block_parameters()[1] - blockParameterWritten = dcmWritten.get_block_parameters()[1] - - self.assertEqual("blockParameter2D", blockParameter.name) - self.assertEqual("Sample block parameters", blockParameter.attrs["description"]) - self.assertEqual("BlockParameterFunction", blockParameter.attrs["function"]) - self.assertEqual("BlockParameterDisplayname", blockParameter.attrs["display_name"]) - self.assertEqual("°C", blockParameter.attrs["units"]) - self.assertEqual(0.75, blockParameter.values[0][0]) - self.assertEqual(-0.25, blockParameter.values[0][1]) - self.assertEqual(0.5, blockParameter.values[0][2]) - self.assertEqual(1.5, blockParameter.values[0][3]) - self.assertEqual(10.75, blockParameter.values[1][0]) - self.assertEqual(-10.25, blockParameter.values[1][1]) - self.assertEqual(10.5, blockParameter.values[1][2]) - self.assertEqual(11.5, blockParameter.values[1][3]) - - self.assertEqual("blockParameter2D", blockParameterWritten.name) - self.assertEqual("Sample block parameters", blockParameterWritten.attrs["description"]) - self.assertEqual("BlockParameterFunction", blockParameterWritten.attrs["function"]) - self.assertEqual("BlockParameterDisplayname", blockParameterWritten.attrs["display_name"]) - self.assertEqual("°C", blockParameterWritten.attrs["units"]) - self.assertEqual(0.75, blockParameterWritten.values[0][0]) - self.assertEqual(-0.25, blockParameterWritten.values[0][1]) - self.assertEqual(0.5, blockParameterWritten.values[0][2]) - self.assertEqual(1.5, blockParameterWritten.values[0][3]) - self.assertEqual(10.75, blockParameterWritten.values[1][0]) - self.assertEqual(-10.25, blockParameterWritten.values[1][1]) - self.assertEqual(10.5, blockParameterWritten.values[1][2]) - self.assertEqual(11.5, blockParameterWritten.values[1][3]) - - -class TestCharacteristicLines(unittest.TestCase): - def test_characteristicLine(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - characteristic = dcm.get_characteristic_lines()[0] - characteristicWritten = dcmWritten.get_characteristic_lines()[0] - - self.assertEqual(1, len(dcm.get_characteristic_lines())) - - self.assertEqual("characteristicLine", characteristic.name) - self.assertEqual("Sample characteristic line", characteristic.attrs["description"]) - self.assertEqual("CharacteristicLineFunction", characteristic.attrs["function"]) - self.assertEqual("CharacteristicLineDisplayname", characteristic.attrs["display_name"]) - self.assertEqual("°", characteristic.unit_values) - self.assertEqual("s", characteristic.attrs["units_x"]) - self.assertEqual(0.0, characteristic.values[0.0]) - self.assertEqual(80.0, characteristic.values[1.0]) - self.assertEqual(120.0, characteristic.values[2.0]) - self.assertEqual(180.0, characteristic.values[3.0]) - self.assertEqual(220.0, characteristic.values[4.0]) - self.assertEqual(260.0, characteristic.values[5.0]) - self.assertEqual(300.0, characteristic.values[6.0]) - self.assertEqual(340.0, characteristic.values[7.0]) - self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) - self.assertEqual("Sample comment", characteristic.attrs["comment"]) - - self.assertEqual(1, len(dcmWritten.get_characteristic_lines())) - - self.assertEqual("characteristicLine", characteristicWritten.name) - self.assertEqual("Sample characteristic line", characteristicWritten.attrs["description"]) - self.assertEqual("CharacteristicLineFunction", characteristicWritten.attrs["function"]) - self.assertEqual("CharacteristicLineDisplayname", characteristicWritten.attrs["display_name"]) - self.assertEqual("°", characteristicWritten.unit_values) - self.assertEqual("s", characteristicWritten.attrs["units_x"]) - self.assertEqual(0.0, characteristicWritten.values[0.0]) - self.assertEqual(80.0, characteristicWritten.values[1.0]) - self.assertEqual(120.0, characteristicWritten.values[2.0]) - self.assertEqual(180.0, characteristicWritten.values[3.0]) - self.assertEqual(220.0, characteristicWritten.values[4.0]) - self.assertEqual(260.0, characteristicWritten.values[5.0]) - self.assertEqual(300.0, characteristicWritten.values[6.0]) - self.assertEqual(340.0, characteristicWritten.values[7.0]) - self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) - self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) - - def test_fixedCharacteristicLine(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - characteristic = dcm.get_fixed_characteristic_lines()[0] - characteristicWritten = dcmWritten.get_fixed_characteristic_lines()[0] - - self.assertEqual(1, len(dcm.get_fixed_characteristic_lines())) - - self.assertEqual("fixedCharacteristicLine", characteristic.name) - self.assertEqual("Sample fixed characteristic line", characteristic.attrs["description"]) - self.assertEqual("FixedCharacteristicLineFunction", characteristic.attrs["function"]) - self.assertEqual("FixedCharacteristicLineDisplayname", characteristic.attrs["display_name"]) - self.assertEqual("°", characteristic.unit_values) - self.assertEqual("s", characteristic.attrs["units_x"]) - self.assertEqual(45.0, characteristic.values[0.0]) - self.assertEqual(90.0, characteristic.values[1.0]) - self.assertEqual(135.0, characteristic.values[2.0]) - self.assertEqual(180.0, characteristic.values[3.0]) - self.assertEqual(225.0, characteristic.values[4.0]) - self.assertEqual(270.0, characteristic.values[5.0]) - self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) - self.assertEqual("Sample comment", characteristic.attrs["comment"]) - - self.assertEqual(1, len(dcmWritten.get_fixed_characteristic_lines())) - - self.assertEqual("fixedCharacteristicLine", characteristicWritten.name) - self.assertEqual("Sample fixed characteristic line", characteristicWritten.attrs["description"]) - self.assertEqual("FixedCharacteristicLineFunction", characteristicWritten.attrs["function"]) - self.assertEqual("FixedCharacteristicLineDisplayname", characteristicWritten.attrs["display_name"]) - self.assertEqual("°", characteristicWritten.unit_values) - self.assertEqual("s", characteristicWritten.attrs["units_x"]) - self.assertEqual(45.0, characteristicWritten.values[0.0]) - self.assertEqual(90.0, characteristicWritten.values[1.0]) - self.assertEqual(135.0, characteristicWritten.values[2.0]) - self.assertEqual(180.0, characteristicWritten.values[3.0]) - self.assertEqual(225.0, characteristicWritten.values[4.0]) - self.assertEqual(270.0, characteristicWritten.values[5.0]) - self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) - self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) - - def test_groupCharacteristicLine(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - characteristic = dcm.get_group_characteristic_lines()[0] - characteristicWritten = dcmWritten.get_group_characteristic_lines()[0] - - self.assertEqual(1, len(dcm.get_group_characteristic_lines())) - - self.assertEqual("groupCharacteristicLine", characteristic.name) - self.assertEqual("Sample group characteristic line", characteristic.attrs["description"]) - self.assertEqual("GroupCharacteristicLineFunction", characteristic.attrs["function"]) - self.assertEqual("GroupCharacteristicLineDisplayname", characteristic.attrs["display_name"]) - self.assertEqual("°", characteristic.unit_values) - self.assertEqual("s", characteristic.attrs["units_x"]) - self.assertEqual(-45.0, characteristic.values[1.0]) - self.assertEqual(-90.0, characteristic.values[2.0]) - self.assertEqual(-135.0, characteristic.values[3.0]) - self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) - self.assertEqual("Sample comment", characteristic.attrs["comment"]) - - self.assertEqual(1, len(dcmWritten.get_group_characteristic_lines())) - - self.assertEqual("groupCharacteristicLine", characteristicWritten.name) - self.assertEqual("Sample group characteristic line", characteristicWritten.attrs["description"]) - self.assertEqual("GroupCharacteristicLineFunction", characteristicWritten.attrs["function"]) - self.assertEqual("GroupCharacteristicLineDisplayname", characteristicWritten.attrs["display_name"]) - self.assertEqual("°", characteristicWritten.unit_values) - self.assertEqual("s", characteristicWritten.attrs["units_x"]) - self.assertEqual(-45.0, characteristicWritten.values[1.0]) - self.assertEqual(-90.0, characteristicWritten.values[2.0]) - self.assertEqual(-135.0, characteristicWritten.values[3.0]) - self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) - self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) - - -class TestCharacteristicMaps(unittest.TestCase): - def test_characteristicMap(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - characteristic = dcm.get_characteristic_maps()[0] - characteristicWritten = dcmWritten.get_characteristic_maps()[0] - - self.assertEqual(1, len(dcm.get_characteristic_maps())) - - self.assertEqual("characteristicMap", characteristic.name) - self.assertEqual("Sample characteristic map", characteristic.attrs["description"]) - self.assertEqual("CharacteristicMapFunction", characteristic.attrs["function"]) - self.assertEqual("CharacteristicMapDisplayname", characteristic.attrs["display_name"]) - self.assertEqual("bar", characteristic.unit_values) - self.assertEqual("°C", characteristic.attrs["units_x"]) - self.assertEqual("m/s", characteristic.attrs["units_y"]) - self.assertEqual(0.0, characteristic.values[1.0][1.0]) - self.assertEqual(0.4, characteristic.values[1.0][2.0]) - self.assertEqual(0.8, characteristic.values[1.0][3.0]) - self.assertEqual(1.0, characteristic.values[1.0][4.0]) - self.assertEqual(1.4, characteristic.values[1.0][5.0]) - self.assertEqual(1.8, characteristic.values[1.0][6.0]) - self.assertEqual(1.0, characteristic.values[2.0][1.0]) - self.assertEqual(2.0, characteristic.values[2.0][2.0]) - self.assertEqual(3.0, characteristic.values[2.0][3.0]) - self.assertEqual(2.0, characteristic.values[2.0][4.0]) - self.assertEqual(3.0, characteristic.values[2.0][5.0]) - self.assertEqual(4.0, characteristic.values[2.0][6.0]) - self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) - self.assertEqual("DISTRIBUTION Y", characteristic.y_mapping) - self.assertEqual("Sample comment", characteristic.attrs["comment"]) - - self.assertEqual(1, len(dcmWritten.get_characteristic_maps())) - - self.assertEqual("characteristicMap", characteristicWritten.name) - self.assertEqual("Sample characteristic map", characteristicWritten.attrs["description"]) - self.assertEqual("CharacteristicMapFunction", characteristicWritten.attrs["function"]) - self.assertEqual("CharacteristicMapDisplayname", characteristicWritten.attrs["display_name"]) - self.assertEqual("bar", characteristicWritten.unit_values) - self.assertEqual("°C", characteristicWritten.attrs["units_x"]) - self.assertEqual("m/s", characteristicWritten.attrs["units_y"]) - self.assertEqual(0.0, characteristicWritten.values[1.0][1.0]) - self.assertEqual(0.4, characteristicWritten.values[1.0][2.0]) - self.assertEqual(0.8, characteristicWritten.values[1.0][3.0]) - self.assertEqual(1.0, characteristicWritten.values[1.0][4.0]) - self.assertEqual(1.4, characteristicWritten.values[1.0][5.0]) - self.assertEqual(1.8, characteristicWritten.values[1.0][6.0]) - self.assertEqual(1.0, characteristicWritten.values[2.0][1.0]) - self.assertEqual(2.0, characteristicWritten.values[2.0][2.0]) - self.assertEqual(3.0, characteristicWritten.values[2.0][3.0]) - self.assertEqual(2.0, characteristicWritten.values[2.0][4.0]) - self.assertEqual(3.0, characteristicWritten.values[2.0][5.0]) - self.assertEqual(4.0, characteristicWritten.values[2.0][6.0]) - self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) - self.assertEqual("DISTRIBUTION Y", characteristicWritten.y_mapping) - self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) - - def test_fixedCharacteristicMap(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - characteristic = dcm.get_fixed_characteristic_maps()[0] - characteristicWritten = dcmWritten.get_fixed_characteristic_maps()[0] - - self.assertEqual(1, len(dcm.get_fixed_characteristic_maps())) - - self.assertEqual("fixedCharacteristicMap", characteristic.name) - self.assertEqual("Sample fixed characteristic map", characteristic.attrs["description"]) - self.assertEqual("FixedCharacteristicMapFunction", characteristic.attrs["function"]) - self.assertEqual("FixedCharacteristicMapDisplayname", characteristic.attrs["display_name"]) - self.assertEqual("bar", characteristic.unit_values) - self.assertEqual("°C", characteristic.attrs["units_x"]) - self.assertEqual("m/s", characteristic.attrs["units_y"]) - self.assertEqual(0.0, characteristic.values[0.0][1.0]) - self.assertEqual(0.4, characteristic.values[0.0][2.0]) - self.assertEqual(0.8, characteristic.values[0.0][3.0]) - self.assertEqual(1.0, characteristic.values[0.0][4.0]) - self.assertEqual(1.4, characteristic.values[0.0][5.0]) - self.assertEqual(1.8, characteristic.values[0.0][6.0]) - self.assertEqual(1.0, characteristic.values[1.0][1.0]) - self.assertEqual(2.0, characteristic.values[1.0][2.0]) - self.assertEqual(3.0, characteristic.values[1.0][3.0]) - self.assertEqual(2.0, characteristic.values[1.0][4.0]) - self.assertEqual(3.0, characteristic.values[1.0][5.0]) - self.assertEqual(4.0, characteristic.values[1.0][6.0]) - self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) - self.assertEqual("DISTRIBUTION Y", characteristic.y_mapping) - self.assertEqual("Sample comment", characteristic.attrs["comment"]) - - self.assertEqual(1, len(dcmWritten.get_fixed_characteristic_maps())) - - self.assertEqual("fixedCharacteristicMap", characteristicWritten.name) - self.assertEqual("Sample fixed characteristic map", characteristicWritten.attrs["description"]) - self.assertEqual("FixedCharacteristicMapFunction", characteristicWritten.attrs["function"]) - self.assertEqual("FixedCharacteristicMapDisplayname", characteristicWritten.attrs["display_name"]) - self.assertEqual("bar", characteristicWritten.unit_values) - self.assertEqual("°C", characteristicWritten.attrs["units_x"]) - self.assertEqual("m/s", characteristicWritten.attrs["units_y"]) - self.assertEqual(0.0, characteristicWritten.values[0.0][1.0]) - self.assertEqual(0.4, characteristicWritten.values[0.0][2.0]) - self.assertEqual(0.8, characteristicWritten.values[0.0][3.0]) - self.assertEqual(1.0, characteristicWritten.values[0.0][4.0]) - self.assertEqual(1.4, characteristicWritten.values[0.0][5.0]) - self.assertEqual(1.8, characteristicWritten.values[0.0][6.0]) - self.assertEqual(1.0, characteristicWritten.values[1.0][1.0]) - self.assertEqual(2.0, characteristicWritten.values[1.0][2.0]) - self.assertEqual(3.0, characteristicWritten.values[1.0][3.0]) - self.assertEqual(2.0, characteristicWritten.values[1.0][4.0]) - self.assertEqual(3.0, characteristicWritten.values[1.0][5.0]) - self.assertEqual(4.0, characteristicWritten.values[1.0][6.0]) - self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) - self.assertEqual("DISTRIBUTION Y", characteristicWritten.y_mapping) - self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) - - def test_groupCharacteristicMap(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - characteristic = dcm.get_group_characteristic_maps()[0] - characteristicWritten = dcmWritten.get_group_characteristic_maps()[0] - - self.assertEqual(1, len(dcm.get_group_characteristic_maps())) - - self.assertEqual("groupCharacteristicMap", characteristic.name) - self.assertEqual("Sample group characteristic map", characteristic.attrs["description"]) - self.assertEqual("GroupCharacteristicMapFunction", characteristic.attrs["function"]) - self.assertEqual("GroupCharacteristicMapDisplayname", characteristic.attrs["display_name"]) - self.assertEqual("bar", characteristic.unit_values) - self.assertEqual("°C", characteristic.attrs["units_x"]) - self.assertEqual("m/s", characteristic.attrs["units_y"]) - self.assertEqual(1.0, characteristic.values[1.0][1.0]) - self.assertEqual(2.0, characteristic.values[1.0][2.0]) - self.assertEqual(3.0, characteristic.values[1.0][3.0]) - self.assertEqual(2.0, characteristic.values[1.0][4.0]) - self.assertEqual(3.0, characteristic.values[1.0][5.0]) - self.assertEqual(4.0, characteristic.values[1.0][6.0]) - self.assertEqual(2.0, characteristic.values[2.0][1.0]) - self.assertEqual(4.0, characteristic.values[2.0][2.0]) - self.assertEqual(6.0, characteristic.values[2.0][3.0]) - self.assertEqual(3.0, characteristic.values[2.0][4.0]) - self.assertEqual(4.0, characteristic.values[2.0][5.0]) - self.assertEqual(5.0, characteristic.values[2.0][6.0]) - self.assertEqual(3.0, characteristic.values[3.0][1.0]) - self.assertEqual(6.0, characteristic.values[3.0][2.0]) - self.assertEqual(9.0, characteristic.values[3.0][3.0]) - self.assertEqual(7.0, characteristic.values[3.0][4.0]) - self.assertEqual(8.0, characteristic.values[3.0][5.0]) - self.assertEqual(9.0, characteristic.values[3.0][6.0]) - self.assertEqual("DISTRIBUTION X", characteristic.x_mapping) - self.assertEqual("DISTRIBUTION Y", characteristic.y_mapping) - self.assertEqual("Sample comment", characteristic.attrs["comment"]) - - self.assertEqual(1, len(dcmWritten.get_group_characteristic_maps())) - - self.assertEqual("groupCharacteristicMap", characteristicWritten.name) - self.assertEqual("Sample group characteristic map", characteristicWritten.attrs["description"]) - self.assertEqual("GroupCharacteristicMapFunction", characteristicWritten.attrs["function"]) - self.assertEqual("GroupCharacteristicMapDisplayname", characteristicWritten.attrs["display_name"]) - self.assertEqual("bar", characteristicWritten.unit_values) - self.assertEqual("°C", characteristicWritten.attrs["units_x"]) - self.assertEqual("m/s", characteristicWritten.attrs["units_y"]) - self.assertEqual(1.0, characteristicWritten.values[1.0][1.0]) - self.assertEqual(2.0, characteristicWritten.values[1.0][2.0]) - self.assertEqual(3.0, characteristicWritten.values[1.0][3.0]) - self.assertEqual(2.0, characteristicWritten.values[1.0][4.0]) - self.assertEqual(3.0, characteristicWritten.values[1.0][5.0]) - self.assertEqual(4.0, characteristicWritten.values[1.0][6.0]) - self.assertEqual(2.0, characteristicWritten.values[2.0][1.0]) - self.assertEqual(4.0, characteristicWritten.values[2.0][2.0]) - self.assertEqual(6.0, characteristicWritten.values[2.0][3.0]) - self.assertEqual(3.0, characteristicWritten.values[2.0][4.0]) - self.assertEqual(4.0, characteristicWritten.values[2.0][5.0]) - self.assertEqual(5.0, characteristicWritten.values[2.0][6.0]) - self.assertEqual(3.0, characteristicWritten.values[3.0][1.0]) - self.assertEqual(6.0, characteristicWritten.values[3.0][2.0]) - self.assertEqual(9.0, characteristicWritten.values[3.0][3.0]) - self.assertEqual(7.0, characteristicWritten.values[3.0][4.0]) - self.assertEqual(8.0, characteristicWritten.values[3.0][5.0]) - self.assertEqual(9.0, characteristicWritten.values[3.0][6.0]) - self.assertEqual("DISTRIBUTION X", characteristicWritten.x_mapping) - self.assertEqual("DISTRIBUTION Y", characteristicWritten.y_mapping) - self.assertEqual("Sample comment", characteristicWritten.attrs["comment"]) - - -class TestDistribution(unittest.TestCase): - def test_distribution(self): - dcm = DcmReader() - dcmWritten = DcmReader() - dcm.read("./Sample.dcm") - dcmWritten.read("./Sample_written.dcm") - distribution = dcm.get_distributions()[0] - distributionWritten = dcmWritten.get_distributions()[0] - - self.assertEqual(1, len(dcm.get_distributions())) - - self.assertEqual("distrib", distribution.name) - self.assertEqual("Sample distribution", distribution.attrs["description"]) - self.assertEqual("DistributionFunction", distribution.attrs["function"]) - self.assertEqual("DistributionDisplayname", distribution.attrs["display_name"]) - self.assertEqual("mm", distribution.attrs["units_x"]) - self.assertEqual(1.0, distribution.values[0]) - self.assertEqual(2.0, distribution.values[1]) - self.assertEqual(3.0, distribution.values[2]) - self.assertEqual("SST", distribution.attrs["comment"]) - - self.assertEqual(1, len(dcmWritten.get_distributions())) - self.assertEqual("distrib", distributionWritten.name) - self.assertEqual("Sample distribution", distributionWritten.attrs["description"]) - self.assertEqual("DistributionFunction", distributionWritten.attrs["function"]) - self.assertEqual("DistributionDisplayname", distributionWritten.attrs["display_name"]) - self.assertEqual("mm", distributionWritten.attrs["units_x"]) - self.assertEqual(1.0, distributionWritten.values[0]) - self.assertEqual(2.0, distributionWritten.values[1]) - self.assertEqual(3.0, distributionWritten.values[2]) - self.assertEqual("SST", distributionWritten.attrs["comment"]) +@pytest.mark.parametrize("path", [r"./Sample.dcm", r"./Sample_written.dcm"]) +@pytest.mark.parametrize( + "name, attrs, values, coords", + [ + ( + "valueParameter", + { + "comment": "Sample comment\nSecond comment line", + "description": "Sample value parameter", + "function": "ParameterFunction", + "display_name": "ParameterDisplayname", + "units": "°C", + "variants": {"VariantA": 27.5}, + }, + np.array(25.0), + (), + ), + ( + "textParameter", + { + "description": "Sample text parameter", + "function": "ParameterFunction", + "display_name": "ParameterDisplayname", + "units": "-", + "variants": {"VariantA": "ParameterB"}, + }, + np.array("ParameterA", dtype=np.dtype(">> # Generate expected results: + >>> name = "valueParameter" + >>> x = dcm[name] + >>> print((x.name, x.attrs, x.values, x.coords)) + """ + + def test_element( + name: str, + attrs: dict, + values: np.ndarray, + coords: tuple[np.ndarray, ...], + characteristic, + ) -> None: + # Check name + np.testing.assert_array_equal(name, characteristic.name) + + # Check attrs: + np.testing.assert_array_equal(len(attrs), len(characteristic.attrs)) + for k, expected in attrs.items(): + actual = characteristic.attrs[k] + np.testing.assert_array_equal(expected, actual) + + # Check values: + if values.dtype.kind in {"U", "S"}: + # Strings: + np.testing.assert_array_equal(values, characteristic.values) + else: + # Numbers: + np.testing.assert_allclose(values, characteristic.values) + + # Check coords: + np.testing.assert_array_equal(len(coords), len(characteristic.coords)) + [np.testing.assert_allclose(e, a) for e, a in zip(coords, characteristic.coords)] + + dcm = DcmReader() + dcmWritten = DcmReader() + # dcm.read("./Sample.dcm") + # dcmWritten.read("./Sample_written.dcm") + + dcm.read(path) + + characteristic = dcm[name] + test_element(name, attrs, values, coords, characteristic) + + # characteristicWritten = dcmWritten[name] + # test_element(name, attrs, values, coords, characteristicWritten) + + # self.assertEqual(1, len(dcm.get_group_characteristic_maps())) + # np.testing.assert_array_equal(1, len(dcmWritten.get_group_characteristic_maps())) if __name__ == "__main__": - unittest.main() + # unittest.main() + pytest.main() From 71935284b0e274d19cc06edebc253c4c6bfde889 Mon Sep 17 00:00:00 2001 From: Illviljan <14371165+Illviljan@users.noreply.github.com> Date: Sun, 19 Mar 2023 17:58:32 +0100 Subject: [PATCH 28/28] Various fixes --- src/dcmReader/dcm_reader.py | 48 ++++-- src/dcmReader/utils.py | 303 +++++++++++++++++++----------------- 2 files changed, 194 insertions(+), 157 deletions(-) diff --git a/src/dcmReader/dcm_reader.py b/src/dcmReader/dcm_reader.py index 0f8d11c..10ef694 100644 --- a/src/dcmReader/dcm_reader.py +++ b/src/dcmReader/dcm_reader.py @@ -9,7 +9,7 @@ from collections import defaultdict from dataclasses import dataclass, field -from typing import TYPE_CHECKING, TypeVar, Any +from typing import TYPE_CHECKING, TypeVar, Any, Generic, Protocol import numpy as np @@ -54,6 +54,7 @@ class DcmReader: _fixed_characteristic_map_list: list[DcmFixedCharacteristicMap] = field(repr=False, default_factory=list) _group_characteristic_map_list: list[DcmGroupCharacteristicMap] = field(repr=False, default_factory=list) _distribution_list: list[DcmDistribution] = field(repr=False, default_factory=list) + _data: dict[str, _DcmBase] = field(repr=False, default_factory=dict) attrs: dict = field(default_factory=dict) def __post_init__(self) -> None: @@ -113,20 +114,20 @@ def _parse_variant(self, line: str, **kwargs) -> dict: or as str if variant is text field """ variant = re.search(r"VAR\s+(.*?)=(.*)", line.strip()) - value = None - if variant is not None: - key = str(variant.group(1)).strip() - value_str = str(variant.group(2)).strip() - try: - # Check if it's number - value = self._convert_value(value_str) - except ValueError: - value = value_str.strip('" ') - - return {key: value} - else: + if variant is None: return {} + key = str(variant.group(1)).strip() + value_str = str(variant.group(2)).strip() + value: str | float | None = None + try: + # Check if it's number + value = self._convert_value(value_str) + except ValueError: + value = value_str.strip('" ') + + return {key: value} + def _parse_block_parameters(self, line: str, **kwargs) -> list: """Parses a block parameters line @@ -357,6 +358,22 @@ def read(self, file) -> None: else: logger.warning("Unknown line detected\n%s", line) + _all = ( + self._parameter_list + + self._block_parameter_list + + self._characteristic_line_list + + self._fixed_characteristic_line_list + + self._group_characteristic_line_list + + self._characteristic_map_list + + self._fixed_characteristic_map_list + + self._group_characteristic_map_list + + self._distribution_list + ) + for v in _all: + if self._data.get(v.name) is not None: + raise NotImplementedError("Duplicated names not supported?") + self._data[v.name] = v + def get_functions(self) -> list: """Returns all found functions as a list""" return self._functions_list @@ -413,7 +430,7 @@ def __str__(self) -> str: output_string += "END\n\n" # Print rest of DCM objects - object_list: list[T_Element] = [] + object_list: list[_DcmBase] = [] object_list.extend(self._parameter_list) object_list.extend(self._block_parameter_list) object_list.extend(self._characteristic_line_list) @@ -428,3 +445,6 @@ def __str__(self) -> str: output_string += f"\n{item}\n" return output_string + + def __getitem__(self, key): + return self._data[key] diff --git a/src/dcmReader/utils.py b/src/dcmReader/utils.py index f470b92..35c5e07 100644 --- a/src/dcmReader/utils.py +++ b/src/dcmReader/utils.py @@ -17,21 +17,159 @@ from numpy.typing import ArrayLike - T_Fmt = Callable[Any, str] + T_Fmt = Callable[..., str] class T_Setting(TypedDict): key_ger: str key_eng: str print_key: str print_format: T_Fmt + print_kwargs: dict[str, Any] parse_key: str parse_method: Callable T_Settings = dict[str, T_Setting] -def fmt_base(x): - return f"{x}" +_COMMENT_QUALIFIER = ("!", "*", ".") + +_SETTINGS: T_Settings = { + k: { + "key_ger": k, + "key_eng": "comment", + "print_key": "*", + "print_format": lambda x: f"{x}", + "print_kwargs": {"pad": 0, "n": 1}, + "parse_key": "", # comment + "parse_method": lambda self: self._parse_comment, + } + for k in _COMMENT_QUALIFIER +} +_SETTINGS.update( + { + "LANGNAME": { + "key_ger": "LANGNAME", + "key_eng": "description", + "print_key": "LANGNAME", + "print_format": lambda x: f'"{x}"', + "print_kwargs": {}, + "parse_key": "description", + "parse_method": lambda self: self._parse_string, + }, + "FUNKTION": { + "key_ger": "FUNKTION", + "key_eng": "function", + "print_key": "FUNKTION", + "print_format": lambda x: f"{x}", + "print_kwargs": {}, + "parse_key": "function", + "parse_method": lambda self: self._parse_string, + }, + "DISPLAYNAME": { + "key_ger": "DISPLAYNAME", + "key_eng": "display_name", + "print_key": "DISPLAYNAME", + "print_format": lambda x: f'"{x}"', + "print_kwargs": {}, + "parse_key": "display_name", + "parse_method": lambda self: self._parse_string, + }, + "EINHEIT_X": { + "key_ger": "EINHEIT_X", + "key_eng": "units_x", + "print_key": "EINHEIT_X", + "print_format": lambda x: f'"{x}"', + "print_kwargs": {}, + "parse_key": "units_x", + "parse_method": lambda self: self._parse_string, + }, + "EINHEIT_Y": { + "key_ger": "EINHEIT_Y", + "key_eng": "units_y", + "print_key": "EINHEIT_Y", + "print_format": lambda x: f'"{x}"', + "print_kwargs": {}, + "parse_key": "units_y", + "parse_method": lambda self: self._parse_string, + }, + "EINHEIT_W": { + "key_ger": "EINHEIT_W", + "key_eng": "units", + "print_key": "EINHEIT_W", + "print_format": lambda x: f'"{x}"', + "print_kwargs": {}, + "parse_key": "units", + "parse_method": lambda self: self._parse_string, + }, + "ST/X": { + "key_ger": "ST/X", + "key_eng": "ST/X", + "print_key": "ST/X", + "print_format": lambda x: f"{x}", + "print_kwargs": {}, + "parse_key": "", + "parse_method": lambda self: self._parse_coord_x, + }, + "ST/Y": { + "key_ger": "ST/Y", + "key_eng": "ST/Y", + "print_key": "ST/Y", + "print_format": lambda x: f"{x}", + "print_kwargs": {}, + "parse_key": "", + "parse_method": lambda self: self._parse_coord_y, + }, + "SSTX": { + # From comments: + "key_ger": "SSTX", + "key_eng": "name_x", + "print_key": "*SSTX", + "print_format": lambda x: f"{x}", + "print_kwargs": {}, + "parse_key": "name_x", + "parse_method": lambda self: self._parse_string, + }, + "SSTY": { + # From comments: + "key_ger": "SSTY", + "key_eng": "name_y", + "print_key": "*SSTY", + "print_format": lambda x: f"{x}", + "print_kwargs": {}, + "parse_key": "name_y", + "parse_method": lambda self: self._parse_string, + }, + "WERT": { + "key_ger": "WERT", + "key_eng": "value", + "print_key": "WERT", + "print_format": lambda x: f"{x}", + "print_kwargs": {}, + "parse_key": "", + "parse_method": lambda self: self._parse_wert, + }, + "TEXT": { + "key_ger": "TEXT", + "key_eng": "text", + "print_key": "TEXT", + "print_format": lambda x: f'"{x}"', + "print_kwargs": {}, + "parse_key": "", + "parse_method": lambda self: self._parse_text, + }, + "VAR": { + "key_ger": "VAR", + "key_eng": "variants", + "print_key": "VAR", + "print_format": lambda x: ", ".join( + [f'{k}="{v}"' if isinstance(v, str) else f"{k}={v}" for k, v in x.items()] + ), + "print_kwargs": {}, + "parse_key": "variants", + "parse_method": lambda self: self._parse_variant, + }, + } +) class _HasValuesProtocol(Protocol): @@ -76,22 +214,25 @@ def __len__(self) -> int: return len(self.values) -def _to_str(val: ArrayLike, fmt: T_Fmt = fmt_base, delimiter: str = " ") -> str: +def _fmt_base(x) -> str: + return f"{x}" + + +def _to_str(val: ArrayLike, fmt: T_Fmt = _fmt_base, delimiter: str = " ") -> str: val_list = np.atleast_1d(val) - # val_list = np.asarray(val) - # return np.array2string(val_list, separator=delimiter).strip("[]") # will use '' :( - # print(val_list) - # fmt = lambda x: f'"{x}"' if isinstance(x, str) else f"{x}" + out = delimiter.join(map(fmt, val_list)) return out - # return delimiter.join(val_list) -def _print_values(key: str, value: ArrayLike, fmt: T_Fmt = fmt_base, n: int = 6, pad: int = 14) -> str: +def _print_values(key: str, value: ArrayLike, fmt: T_Fmt = _fmt_base, n: int = 6, pad: int = 14) -> str: """Print pairwise values prettily.""" # Normalize so that value is always a list: value_list = np.atleast_1d(value) - # value_list = np.asarray(value) + + # Split strings that contains a newline: + if value_list.dtype.kind in {"U", "S"}: + value_list = np.array("\n".join(value_list).split("\n")) # Split too long lists into chunks. value_list_chunked = [value_list[i : i + n] for i in range(0, len(value_list), n)] @@ -100,136 +241,11 @@ def _print_values(key: str, value: ArrayLike, fmt: T_Fmt = fmt_base, n: int = 6, pad_ = f"<{pad}" out = "" for v in value_list_chunked: - out += f" {key:{pad_}}{_to_str(v, fmt)}\n" + value_str = _to_str(v, fmt) + out += f" {key:{pad_}}{value_str}\n" return out -_COMMENT_QUALIFIER = ("!", "*", ".") - -_SETTINGS: T_Settings = { - "LANGNAME": { - "key_ger": "LANGNAME", - "key_eng": "description", - "print_key": "LANGNAME", - "print_format": lambda x: f'"{x}"', - "parse_key": "description", - "parse_method": lambda self: self._parse_string, - }, - "FUNKTION": { - "key_ger": "FUNKTION", - "key_eng": "function", - "print_key": "FUNKTION", - "print_format": lambda x: f"{x}", - "parse_key": "function", - "parse_method": lambda self: self._parse_string, - }, - "DISPLAYNAME": { - "key_ger": "DISPLAYNAME", - "key_eng": "display_name", - "print_key": "DISPLAYNAME", - "print_format": lambda x: f'"{x}"', - "parse_key": "display_name", - "parse_method": lambda self: self._parse_string, - }, - "EINHEIT_X": { - "key_ger": "EINHEIT_X", - "key_eng": "units_x", - "print_key": "EINHEIT_X", - "print_format": lambda x: f'"{x}"', - "parse_key": "units_x", - "parse_method": lambda self: self._parse_string, - }, - "EINHEIT_Y": { - "key_ger": "EINHEIT_Y", - "key_eng": "units_y", - "print_key": "EINHEIT_Y", - "print_format": lambda x: f'"{x}"', - "parse_key": "units_y", - "parse_method": lambda self: self._parse_string, - }, - "EINHEIT_W": { - "key_ger": "EINHEIT_W", - "key_eng": "units", - "print_key": "EINHEIT_W", - "print_format": lambda x: f'"{x}"', - "parse_key": "units", - "parse_method": lambda self: self._parse_string, - }, - "ST/X": { - "key_ger": "ST/X", - "key_eng": "ST/X", - "print_key": "ST/X", - "print_format": lambda x: f"{x}", - "parse_key": "", - "parse_method": lambda self: self._parse_coord_x, - }, - "ST/Y": { - "key_ger": "ST/Y", - "key_eng": "ST/Y", - "print_key": "ST/Y", - "print_format": lambda x: f"{x}", - "parse_key": "", - "parse_method": lambda self: self._parse_coord_y, - }, - "SSTX": { - # From comments: - "key_ger": "SSTX", - "key_eng": "name_x", - "print_key": "*SSTY", - "print_format": lambda x: f"{x}", - "parse_key": "name_x", - "parse_method": lambda self: self._parse_string, - }, - "SSTY": { - # From comments: - "key_ger": "SSTY", - "key_eng": "name_y", - "print_key": "*SSTY", - "print_format": lambda x: f"{x}", - "parse_key": "name_y", - "parse_method": lambda self: self._parse_string, - }, - "WERT": { - "key_ger": "WERT", - "key_eng": "value", - "print_key": "WERT", - "print_format": lambda x: f"{x}", - "parse_key": "", - "parse_method": lambda self: self._parse_wert, - }, - "TEXT": { - "key_ger": "TEXT", - "key_eng": "text", - "print_key": "TEXT", - "print_format": lambda x: f'"{x}"', - "parse_key": "", - "parse_method": lambda self: self._parse_text, - }, - "VAR": { - "key_ger": "VAR", - "key_eng": "variants", - "print_key": "VAR", - "print_format": lambda x: ", ".join([f'{k}="{v}"' if isinstance(v, str) else f"{k}={v}" for k, v in x.items()]), - "parse_key": "variants", - "parse_method": lambda self: self._parse_variant, - }, -} -_SETTINGS.update( - { - k: { - "key_ger": k, - "key_eng": k, - # "print_key": k, - "print_format": lambda x: f'"{x}"', - # "print_kwargs": {"pad": 0, "n": 1}, - "parse_key": "", - "parse_method": lambda self: self._parse_comment, - } - for k in _COMMENT_QUALIFIER - } -) - - @dataclass class _DcmBase(ShapeRelatedMixin): name: str @@ -239,7 +255,7 @@ class _DcmBase(ShapeRelatedMixin): attrs: dict = field(default_factory=dict) element_syntax: str = "" - def __lt__(self, other): + def __lt__(self, other) -> bool: self_function = self.attrs.get("function", "") self_description = self.attrs.get("description", "") other_function = other.attrs.get("function", "") @@ -255,9 +271,9 @@ def _print_attrs(self, k: str, value: str) -> str: print_key = setting["print_key"] print_val = attrs_value print_format = setting["print_format"] - kws: dict[str, Any] = {} # TODO: Remove? Currently only needed if comments were printed. + print_kwargs = setting["print_kwargs"] - value += _print_values(print_key, print_val, print_format, **kws) + value += _print_values(print_key, print_val, print_format, **print_kwargs) return value @@ -274,11 +290,12 @@ def _print_dcm_format(self) -> str: ncoords = len(coords_rev) # Header: - value = f"{self.element_syntax} {self.name} {_to_str(shape_rev)}\n" + shape_str = _to_str(shape_rev) + value = f"{self.element_syntax} {self.name} {shape_str}\n" # Attributes printed before the values: for k in _SETTINGS.keys(): - if k in ("WERT", "ST/X", "ST/Y", "VAR"): + if k in ("WERT", "ST/X", "ST/Y", "VAR") + _COMMENT_QUALIFIER[:-1]: continue value = self._print_attrs(k, value)