• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

EIT-ALIVE / eitprocessing / 12633263274

06 Jan 2025 01:20PM UTC coverage: 83.698% (+2.1%) from 81.598%
12633263274

push

github

psomhorst
Bump version: 1.5.1 → 1.5.2

347 of 454 branches covered (76.43%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

9 existing lines in 4 files now uncovered.

1373 of 1601 relevant lines covered (85.76%)

0.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.74
/eitprocessing/datahandling/eitdata.py
1
from __future__ import annotations
1✔
2

3
import warnings
1✔
4
from dataclasses import dataclass, field
1✔
5
from enum import auto
1✔
6
from pathlib import Path
1✔
7
from typing import TYPE_CHECKING, TypeVar
1✔
8

9
import numpy as np
1✔
10
from strenum import LowercaseStrEnum
1✔
11

12
from eitprocessing.datahandling import DataContainer
1✔
13
from eitprocessing.datahandling.mixins.slicing import SelectByTime
1✔
14

15
if TYPE_CHECKING:
16
    from typing_extensions import Self
17

18

19
T = TypeVar("T", bound="EITData")
1✔
20

21

22
@dataclass(eq=False)
1✔
23
class EITData(DataContainer, SelectByTime):
1✔
24
    """Container for EIT impedance data.
25

26
    This class holds the pixel impedance from an EIT measurement, as well as metadata describing the measurement. The
27
    class is meant to hold data from (part of) a singular continuous measurement.
28

29
    This class can't be initialized directly. Instead, use `load_eit_data(<path>, vendor=<vendor>)` to load data from
30
    disk.
31

32
    Args:
33
        path: The path of list of paths of the source from which data was derived.
34
        nframes: Number of frames.
35
        time: The time of each frame (since start measurement).
36
        sample_frequency: The (average) frequency at which the frames are collected, in Hz.
37
        vendor: The vendor of the device the data was collected with.
38
        label: Computer readable label identifying this dataset.
39
        name: Human readable name for the data.
40
        pixel_impedance: Impedance values for each pixel at each frame.
41
    """  # TODO: fix docstring
42

43
    path: str | Path | list[Path | str] = field(compare=False, repr=False)
1✔
44
    nframes: int = field(repr=False)
1✔
45
    time: np.ndarray = field(repr=False)
1✔
46
    sample_frequency: float = field(metadata={"check_equivalence": True}, repr=False)
1✔
47
    vendor: Vendor = field(metadata={"check_equivalence": True}, repr=False)
1✔
48
    label: str | None = field(default=None, compare=False, metadata={"check_equivalence": True})
1✔
49
    description: str = field(default="", compare=False, repr=False)
1✔
50
    name: str | None = field(default=None, compare=False, repr=False)
1✔
51
    pixel_impedance: np.ndarray = field(repr=False, kw_only=True)
1✔
52

53
    def __post_init__(self):
1✔
54
        if not self.label:
1!
UNCOV
55
            self.label = f"{self.__class__.__name__}_{id(self)}"
×
56

57
        self.path = self.ensure_path_list(self.path)
1✔
58
        if len(self.path) == 1:
1✔
59
            self.path = self.path[0]
1✔
60

61
        self.name = self.name or self.label
1✔
62

63
        if (lv := len(self.pixel_impedance)) != (lt := len(self.time)):
1!
64
            msg = f"The number of time points ({lt}) does not match the number of pixel impedance values ({lv})."
×
UNCOV
65
            raise ValueError(msg)
×
66

67
    @property
1✔
68
    def framerate(self) -> float:
1✔
69
        """Deprecated alias to `sample_frequency`."""
UNCOV
70
        warnings.warn(
×
71
            "The `framerate` attribute has been deprecated. Use `sample_frequency` instead.",
72
            DeprecationWarning,
73
        )
UNCOV
74
        return self.sample_frequency
×
75

76
    @staticmethod
1✔
77
    def ensure_path_list(
1✔
78
        path: str | Path | list[str | Path],
79
    ) -> list[Path]:
80
        """Return the path or paths as a list.
81

82
        The path of any EITData object can be a single str/Path or a list of str/Path objects. This method returns a
83
        list of Path objects given either a str/Path or list of str/Paths.
84
        """
85
        if isinstance(path, list):
1✔
86
            return [Path(p) for p in path]
1✔
87
        return [Path(path)]
1✔
88

89
    def __add__(self: T, other: T) -> T:
1✔
UNCOV
90
        return self.concatenate(other)
×
91

92
    def concatenate(self: T, other: T, newlabel: str | None = None) -> T:  # noqa: D102, will be moved to mixin in future
1✔
93
        # Check that data can be concatenated
94
        self.isequivalent(other, raise_=True)
1✔
95
        if np.min(other.time) <= np.max(self.time):
1✔
96
            msg = f"Concatenation failed. Second dataset ({other.name}) may not start before first ({self.name}) ends."
1✔
97
            raise ValueError(msg)
1✔
98

99
        self_path = self.ensure_path_list(self.path)
1✔
100
        other_path = self.ensure_path_list(other.path)
1✔
101
        newlabel = newlabel or f"Merge of <{self.label}> and <{other.label}>"
1✔
102

103
        return self.__class__(
1✔
104
            vendor=self.vendor,
105
            path=[*self_path, *other_path],
106
            label=self.label,  # TODO: using newlabel leads to errors
107
            sample_frequency=self.sample_frequency,
108
            nframes=self.nframes + other.nframes,
109
            time=np.concatenate((self.time, other.time)),
110
            pixel_impedance=np.concatenate((self.pixel_impedance, other.pixel_impedance), axis=0),
111
        )
112

113
    def _sliced_copy(
1✔
114
        self,
115
        start_index: int,
116
        end_index: int,
117
        newlabel: str,  # noqa: ARG002
118
    ) -> Self:
119
        cls = self.__class__
1✔
120
        time = np.copy(self.time[start_index:end_index])
1✔
121
        nframes = len(time)
1✔
122

123
        pixel_impedance = np.copy(self.pixel_impedance[start_index:end_index, :, :])
1✔
124

125
        return cls(
1✔
126
            path=self.path,
127
            nframes=nframes,
128
            vendor=self.vendor,
129
            time=time,
130
            sample_frequency=self.sample_frequency,
131
            label=self.label,  # newlabel gives errors
132
            pixel_impedance=pixel_impedance,
133
        )
134

135
    def __len__(self):
1✔
136
        return self.pixel_impedance.shape[0]
1✔
137

138
    def calculate_global_impedance(self) -> np.ndarray:
1✔
139
        """Return the global impedance, i.e. the sum of all included pixels at each frame."""
140
        return np.nansum(self.pixel_impedance, axis=(1, 2))
1✔
141

142

143
class Vendor(LowercaseStrEnum):
1✔
144
    """Enum indicating the vendor (manufacturer) of the source EIT device."""
145

146
    DRAEGER = auto()
1✔
147
    TIMPEL = auto()
1✔
148
    SENTEC = auto()
1✔
149
    DRAGER = DRAEGER
1✔
150
    DRÄGER = DRAEGER  # noqa: PLC2401
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc