gamry-dta 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gamry_dta-0.1.0/PKG-INFO +40 -0
- gamry_dta-0.1.0/README.md +26 -0
- gamry_dta-0.1.0/pyproject.toml +25 -0
- gamry_dta-0.1.0/pyproject.toml.orig +25 -0
- gamry_dta-0.1.0/src/gamrydta/__init__.py +17 -0
- gamry_dta-0.1.0/src/gamrydta/aliases.py +5 -0
- gamry_dta-0.1.0/src/gamrydta/common.py +26 -0
- gamry_dta-0.1.0/src/gamrydta/experiments/__init__.py +62 -0
- gamry_dta-0.1.0/src/gamrydta/experiments/chrono.py +19 -0
- gamry_dta-0.1.0/src/gamrydta/experiments/experiment.py +81 -0
- gamry_dta-0.1.0/src/gamrydta/experiments/impedance.py +9 -0
- gamry_dta-0.1.0/src/gamrydta/experiments/opencircuit.py +9 -0
- gamry_dta-0.1.0/src/gamrydta/experiments/voltammetry.py +13 -0
- gamry_dta-0.1.0/src/gamrydta/parsing/__init__.py +3 -0
- gamry_dta-0.1.0/src/gamrydta/parsing/curves.py +39 -0
- gamry_dta-0.1.0/src/gamrydta/parsing/dta.py +26 -0
- gamry_dta-0.1.0/src/gamrydta/parsing/header.py +26 -0
- gamry_dta-0.1.0/src/gamrydta/parsing/ocv.py +36 -0
- gamry_dta-0.1.0/src/gamrydta/parsing/table.py +35 -0
- gamry_dta-0.1.0/src/gamrydta/variables.py +56 -0
gamry_dta-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: gamry-dta
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Read Gamry .DTA files as polars dataframes. Also extract metadata as dictionaries.
|
|
5
|
+
Author: kristianfkt
|
|
6
|
+
Author-email: kristianfkt <kristianfkt@gmail.com>
|
|
7
|
+
Requires-Dist: pandas>=3.0.6
|
|
8
|
+
Requires-Dist: polars>=1.42.0
|
|
9
|
+
Requires-Dist: pyarrow>=25.0.1
|
|
10
|
+
Requires-Dist: matplotlib>=3.11.2 ; extra == 'plot'
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Provides-Extra: plot
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Gamry DTA
|
|
16
|
+
- Library for reading gamry DTA files
|
|
17
|
+
|
|
18
|
+
# Installing
|
|
19
|
+
`pip install gamrydta`
|
|
20
|
+
|
|
21
|
+
# Quick start
|
|
22
|
+
```
|
|
23
|
+
import gamrydta
|
|
24
|
+
|
|
25
|
+
path = ".../some_file.dta"
|
|
26
|
+
dta = gamrydta.read()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
# Contributing
|
|
30
|
+
- Bug reports
|
|
31
|
+
- ...
|
|
32
|
+
|
|
33
|
+
# Future:
|
|
34
|
+
- Parse column name-units according to https://github.com/emmo-repo/domain-electrochemistry
|
|
35
|
+
- Parse header fields according to https://github.com/emmo-repo/domain-electrochemistry
|
|
36
|
+
- Infer header field types, text, float, etc
|
|
37
|
+
- Add retry-cases for encoding errors
|
|
38
|
+
- Set default 'decimal' to None. Infer if not provided.
|
|
39
|
+
- Add an explorative dashboardu
|
|
40
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Gamry DTA
|
|
2
|
+
- Library for reading gamry DTA files
|
|
3
|
+
|
|
4
|
+
# Installing
|
|
5
|
+
`pip install gamrydta`
|
|
6
|
+
|
|
7
|
+
# Quick start
|
|
8
|
+
```
|
|
9
|
+
import gamrydta
|
|
10
|
+
|
|
11
|
+
path = ".../some_file.dta"
|
|
12
|
+
dta = gamrydta.read()
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
# Contributing
|
|
16
|
+
- Bug reports
|
|
17
|
+
- ...
|
|
18
|
+
|
|
19
|
+
# Future:
|
|
20
|
+
- Parse column name-units according to https://github.com/emmo-repo/domain-electrochemistry
|
|
21
|
+
- Parse header fields according to https://github.com/emmo-repo/domain-electrochemistry
|
|
22
|
+
- Infer header field types, text, float, etc
|
|
23
|
+
- Add retry-cases for encoding errors
|
|
24
|
+
- Set default 'decimal' to None. Infer if not provided.
|
|
25
|
+
- Add an explorative dashboardu
|
|
26
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gamry-dta"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Read Gamry .DTA files as polars dataframes. Also extract metadata as dictionaries."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pandas>=3.0.6",
|
|
9
|
+
"polars>=1.42.0",
|
|
10
|
+
"pyarrow>=25.0.1",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[[project.authors]]
|
|
14
|
+
name = "kristianfkt"
|
|
15
|
+
email = "kristianfkt@gmail.com"
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
plot = ["matplotlib>=3.11.2"]
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["uv_build>=0.11.2,<0.12.0"]
|
|
22
|
+
build-backend = "uv_build"
|
|
23
|
+
|
|
24
|
+
[tool.uv.build-backend]
|
|
25
|
+
module-name = "gamrydta"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gamry-dta"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Read Gamry .DTA files as polars dataframes. Also extract metadata as dictionaries."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "kristianfkt", email = "kristianfkt@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"pandas>=3.0.6",
|
|
12
|
+
"polars>=1.42.0",
|
|
13
|
+
"pyarrow>=25.0.1",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
plot = [
|
|
18
|
+
"matplotlib>=3.11.2",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["uv_build>=0.11.2,<0.12.0"]
|
|
23
|
+
build-backend = "uv_build"
|
|
24
|
+
[tool.uv.build-backend]
|
|
25
|
+
module-name = "gamrydta"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from gamrydta.experiments import read
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def find(
|
|
7
|
+
path: str | Path,
|
|
8
|
+
pattern: str = "*.DTA",
|
|
9
|
+
) -> list[Path]:
|
|
10
|
+
if isinstance(path, str):
|
|
11
|
+
path = Path(path)
|
|
12
|
+
|
|
13
|
+
files = list(path.rglob(pattern))
|
|
14
|
+
return files
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__all__ = ["find", "read"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
Decimal = Literal[".", ","]
|
|
4
|
+
DECIMAL = "."
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ParsingError(Exception):
|
|
8
|
+
"""
|
|
9
|
+
Parsing somehow failed
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_text(value: str):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def is_float(value: str):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_int(value: str):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_datetime(value: str):
|
|
26
|
+
pass
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from gamrydta import parsing
|
|
5
|
+
from gamrydta.common import DECIMAL, Decimal, ParsingError
|
|
6
|
+
from gamrydta.experiments.chrono import (
|
|
7
|
+
Chronoamperometry,
|
|
8
|
+
Chronocoulometry,
|
|
9
|
+
Chronopotentiometry,
|
|
10
|
+
)
|
|
11
|
+
from gamrydta.experiments.experiment import Experiment
|
|
12
|
+
from gamrydta.experiments.impedance import PotentiostaticEIS
|
|
13
|
+
from gamrydta.experiments.opencircuit import CorrosionPotential, OpenCircuitPotential
|
|
14
|
+
from gamrydta.experiments.voltammetry import CyclicVoltammetry, LinearSweepVoltammetry
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
EXPERIMENTS = {
|
|
19
|
+
"CHRONOA": Chronoamperometry,
|
|
20
|
+
"CHRONOP": Chronopotentiometry,
|
|
21
|
+
"CHRONOC": Chronocoulometry,
|
|
22
|
+
"CV": CyclicVoltammetry,
|
|
23
|
+
"LSV": LinearSweepVoltammetry,
|
|
24
|
+
"OCP": OpenCircuitPotential,
|
|
25
|
+
"CORPOT": CorrosionPotential,
|
|
26
|
+
"EISPOT": PotentiostaticEIS,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def read(
|
|
31
|
+
path: str | Path,
|
|
32
|
+
encoding: str | None = None,
|
|
33
|
+
decimal: Decimal = DECIMAL,
|
|
34
|
+
):
|
|
35
|
+
for line in parsing.dta.scan(path, encoding=encoding):
|
|
36
|
+
if line[0] == "TAG":
|
|
37
|
+
tag = line[1]
|
|
38
|
+
break
|
|
39
|
+
else:
|
|
40
|
+
raise ParsingError("Failed to detect experiement TAG")
|
|
41
|
+
|
|
42
|
+
if tag in EXPERIMENTS:
|
|
43
|
+
experiment = EXPERIMENTS[tag]
|
|
44
|
+
logger.info(f"TAG {tag} known - returning {experiment}")
|
|
45
|
+
else:
|
|
46
|
+
experiment = Experiment
|
|
47
|
+
logger.info(f"TAG {tag} unknonw - returning {experiment}")
|
|
48
|
+
return experiment.read(path, encoding=encoding, decimal=decimal)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"Chronoamperometry",
|
|
53
|
+
"Chronocoulometry",
|
|
54
|
+
"Chronopotentiometry",
|
|
55
|
+
"CorrosionPotential",
|
|
56
|
+
"CyclicVoltammetry",
|
|
57
|
+
"DTAData",
|
|
58
|
+
"LinearSweepVoltammetry",
|
|
59
|
+
"OpenCircuitPotential",
|
|
60
|
+
"PotentiostaticEIS",
|
|
61
|
+
"read",
|
|
62
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from gamrydta.experiments.experiment import Experiment
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Chronoamperometry(Experiment):
|
|
5
|
+
@property
|
|
6
|
+
def current(self) -> float:
|
|
7
|
+
return self._header["current"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Chronopotentiometry(Experiment):
|
|
11
|
+
@property
|
|
12
|
+
def current(self) -> float:
|
|
13
|
+
return self._header["current"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Chronocoulometry(Experiment):
|
|
17
|
+
@property
|
|
18
|
+
def current(self) -> float:
|
|
19
|
+
return self._header["current"]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Self
|
|
4
|
+
|
|
5
|
+
import polars as pl
|
|
6
|
+
|
|
7
|
+
from gamrydta import parsing
|
|
8
|
+
from gamrydta.common import DECIMAL, Decimal
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Experiment:
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
*,
|
|
15
|
+
path: str | Path,
|
|
16
|
+
curves: pl.DataFrame,
|
|
17
|
+
header: Mapping[str, Any],
|
|
18
|
+
ocv: pl.DataFrame | None,
|
|
19
|
+
):
|
|
20
|
+
self._path = parsing.dta.resolve(path)
|
|
21
|
+
self._curves = curves
|
|
22
|
+
self._header = header
|
|
23
|
+
self._ocv = ocv
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def read(
|
|
27
|
+
cls,
|
|
28
|
+
path: str | Path,
|
|
29
|
+
encoding: str | None = None,
|
|
30
|
+
decimal: Decimal = DECIMAL,
|
|
31
|
+
) -> Self:
|
|
32
|
+
|
|
33
|
+
path = parsing.dta.resolve(path)
|
|
34
|
+
lines = parsing.dta.read(path, encoding=encoding)
|
|
35
|
+
|
|
36
|
+
return cls(
|
|
37
|
+
path=path,
|
|
38
|
+
curves=parsing.curves.parse(lines, decimal=decimal),
|
|
39
|
+
header=parsing.header.parse(lines, decimal=decimal),
|
|
40
|
+
ocv=parsing.ocv.parse(lines, decimal=decimal),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def path(self) -> Path:
|
|
45
|
+
return self._path
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def curves(self) -> pl.DataFrame:
|
|
49
|
+
return self._curves
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def data(self) -> pl.DataFrame:
|
|
53
|
+
return self._curves
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def header(self) -> Mapping[str, Any]:
|
|
57
|
+
return self._header
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def metadata(self) -> Mapping[str, Any]:
|
|
61
|
+
return self._header
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def experiment(self) -> str:
|
|
65
|
+
return self._header["TITLE"]
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def tag(self) -> str:
|
|
69
|
+
return self._header["TAG"]
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def ocv(self) -> pl.DataFrame | None:
|
|
73
|
+
return self._ocv
|
|
74
|
+
|
|
75
|
+
def to_dict(self) -> dict:
|
|
76
|
+
return {
|
|
77
|
+
"path": self.path,
|
|
78
|
+
"header": self.header,
|
|
79
|
+
"curve": self.curves,
|
|
80
|
+
"ocv": self.ocv,
|
|
81
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from gamrydta.experiments.experiment import Experiment
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CyclicVoltammetry(Experiment):
|
|
5
|
+
@property
|
|
6
|
+
def sweeprate(self) -> float:
|
|
7
|
+
return self._header["SWEEPRATE"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LinearSweepVoltammetry(Experiment):
|
|
11
|
+
@property
|
|
12
|
+
def sweeprate(self) -> float:
|
|
13
|
+
return self._header["SWEEPRATE"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
from gamrydta import parsing
|
|
6
|
+
from gamrydta.aliases import Lines
|
|
7
|
+
from gamrydta.common import DECIMAL, Decimal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def match(line: list[str]) -> bool:
|
|
11
|
+
return bool(re.fullmatch(r"(?:ZCURVE|CURVE(?:[1-9][0-9]*)?)", line[0]))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse(
|
|
15
|
+
lines: Lines,
|
|
16
|
+
decimal: Decimal = DECIMAL,
|
|
17
|
+
) -> pl.DataFrame:
|
|
18
|
+
"""
|
|
19
|
+
Extracts the curve data from a DTA file and returns it as a DataFrame.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
indices_start = [idx + 1 for idx, line in enumerate(lines) if match(line)]
|
|
23
|
+
indices_end = [idx - 1 for idx in indices_start[1::]] + [len(lines)]
|
|
24
|
+
curves = []
|
|
25
|
+
for s, e in zip(indices_start, indices_end):
|
|
26
|
+
curve = parsing.table.parse(
|
|
27
|
+
lines[s:e],
|
|
28
|
+
decimal=decimal,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
curves.append(curve)
|
|
32
|
+
|
|
33
|
+
if len(curves) > 1:
|
|
34
|
+
curves = [
|
|
35
|
+
curve.with_columns(pl.lit(i).alias("curve"))
|
|
36
|
+
for i, curve in enumerate(curves, start=1)
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
return pl.concat(curves)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def resolve(path: str | Path) -> Path:
|
|
6
|
+
"""
|
|
7
|
+
Ensures that a path is Path.only.
|
|
8
|
+
"""
|
|
9
|
+
return Path(path).resolve(strict=True)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def split(line: str) -> list[str]:
|
|
13
|
+
return line.strip().removesuffix("\n").split("\t")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def read(path: str | Path, encoding: str | None) -> list[list[str]]:
|
|
17
|
+
path = resolve(path)
|
|
18
|
+
with open(path, "r", encoding=encoding) as f:
|
|
19
|
+
return [split(line) for line in f]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def scan(path: str | Path, encoding: str | None) -> Iterator[list[str]]:
|
|
23
|
+
path = resolve(path)
|
|
24
|
+
with open(path, "r", encoding=encoding) as f:
|
|
25
|
+
for line in f:
|
|
26
|
+
yield split(line)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from gamrydta.aliases import Lines
|
|
2
|
+
from gamrydta.common import DECIMAL, Decimal
|
|
3
|
+
from gamrydta.parsing import curves, ocv
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse(
|
|
7
|
+
lines: Lines,
|
|
8
|
+
decimal: Decimal = DECIMAL,
|
|
9
|
+
) -> dict:
|
|
10
|
+
"""
|
|
11
|
+
Extracts the header fields from a DTA file and returns them as a dictionary.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
header = {}
|
|
15
|
+
for line in lines:
|
|
16
|
+
if curves.match(line):
|
|
17
|
+
return header
|
|
18
|
+
if not line[0].strip() or ocv.match(line):
|
|
19
|
+
continue
|
|
20
|
+
if len(line) == 2:
|
|
21
|
+
key, value = line
|
|
22
|
+
header[key] = value
|
|
23
|
+
if len(line) == 4:
|
|
24
|
+
code, kind, value, name = line
|
|
25
|
+
header[name] = value
|
|
26
|
+
return header
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
from gamrydta import parsing
|
|
6
|
+
from gamrydta.aliases import Lines
|
|
7
|
+
from gamrydta.common import DECIMAL, Decimal, ParsingError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def match(line: list[str]) -> bool:
|
|
11
|
+
return line[0] == "OCVCURVE"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse(
|
|
15
|
+
lines: Lines,
|
|
16
|
+
decimal: Decimal = DECIMAL,
|
|
17
|
+
) -> pl.DataFrame | None:
|
|
18
|
+
"""
|
|
19
|
+
Extracts the OCV data from a DTA file and returns it as a DataFrame.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
for idx, line in enumerate(lines):
|
|
23
|
+
if match(line):
|
|
24
|
+
index_start = idx + 1
|
|
25
|
+
break
|
|
26
|
+
else:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
for idx, line in enumerate(lines[index_start + 2 : :], start=index_start + 2):
|
|
30
|
+
if not bool(re.fullmatch(r"[0-9]+", line[0])):
|
|
31
|
+
index_end = idx
|
|
32
|
+
break
|
|
33
|
+
else:
|
|
34
|
+
raise ParsingError("Reached en of lines without findingn OCV curve end")
|
|
35
|
+
|
|
36
|
+
return parsing.table.parse(lines[index_start:index_end], decimal=decimal)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
from io import StringIO
|
|
3
|
+
|
|
4
|
+
import polars as pl
|
|
5
|
+
|
|
6
|
+
from gamrydta.aliases import Lines
|
|
7
|
+
from gamrydta.common import DECIMAL, Decimal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def parse(
|
|
11
|
+
table: Lines,
|
|
12
|
+
decimal: Decimal = DECIMAL,
|
|
13
|
+
) -> pl.DataFrame:
|
|
14
|
+
"""
|
|
15
|
+
Parses lines from
|
|
16
|
+
CURVE TABLE
|
|
17
|
+
..units
|
|
18
|
+
...vvalues
|
|
19
|
+
"""
|
|
20
|
+
names = table[0]
|
|
21
|
+
units = table[1]
|
|
22
|
+
|
|
23
|
+
buffer = StringIO()
|
|
24
|
+
writer = csv.writer(buffer, delimiter="\t")
|
|
25
|
+
writer.writerow([f"{name} / {unit}" for name, unit in zip(names, units)])
|
|
26
|
+
writer.writerows(table[2::])
|
|
27
|
+
buffer.seek(0)
|
|
28
|
+
return pl.read_csv(
|
|
29
|
+
buffer,
|
|
30
|
+
has_header=True,
|
|
31
|
+
infer_schema=True,
|
|
32
|
+
infer_schema_length=None,
|
|
33
|
+
separator="\t",
|
|
34
|
+
decimal_comma=(decimal == ","),
|
|
35
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import TypedDict
|
|
3
|
+
|
|
4
|
+
import polars as pl
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Name(TypedDict):
|
|
8
|
+
code: str
|
|
9
|
+
label: str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Unit(TypedDict):
|
|
13
|
+
code: str
|
|
14
|
+
label: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Variable:
|
|
19
|
+
name: Name
|
|
20
|
+
unit: Unit
|
|
21
|
+
code: str = field(init=False)
|
|
22
|
+
label: str = field(init=False)
|
|
23
|
+
|
|
24
|
+
def __post_init__(self):
|
|
25
|
+
self.code = f"{self.name['code']}_{self.unit['code']}"
|
|
26
|
+
|
|
27
|
+
self.label = f"{self.name['label']} / {self.unit['label']}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
UNITS = {
|
|
31
|
+
"#": Unit(code="1", label="1"),
|
|
32
|
+
"s": Unit(code="second", label="s"),
|
|
33
|
+
"V vs. Ref.": Unit(code="voltage_versus_reference", label="V vs Ref"),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
VARIABLES: dict[str, str] = {
|
|
38
|
+
"Pt": "Record count / 1",
|
|
39
|
+
"T": "Test time / s",
|
|
40
|
+
"curve": "Cycle count / 1",
|
|
41
|
+
"Vf": "Voltage / V",
|
|
42
|
+
"Im": "Current / A",
|
|
43
|
+
"Q": "Charge / C",
|
|
44
|
+
"Freq": "Frequency / Hz",
|
|
45
|
+
"Zreal": "Zr / Ohm",
|
|
46
|
+
"Zimag": "Zi / Ohm",
|
|
47
|
+
"Zmod": "Zmod / Ohm",
|
|
48
|
+
"Zphz": "Zphz / degrees",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def rename(data: pl.DataFrame) -> pl.DataFrame:
|
|
53
|
+
"""
|
|
54
|
+
Rename some mapped variables to better described names
|
|
55
|
+
"""
|
|
56
|
+
return data.rename(VARIABLES, strict=False)
|