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

EIT-ALIVE / eitprocessing / 12120635114

02 Dec 2024 01:44PM UTC coverage: 81.489% (-0.6%) from 82.129%
12120635114

push

github

actions-user
Bump version: 1.4.6 → 1.4.7

344 of 468 branches covered (73.5%)

Branch coverage included in aggregate %.

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

36 existing lines in 5 files now uncovered.

1364 of 1628 relevant lines covered (83.78%)

0.84 hits per line

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

89.61
/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
    name: str | None = field(default=None, compare=False, repr=False)
1✔
50
    pixel_impedance: np.ndarray = field(repr=False, kw_only=True)
1✔
51

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

141

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

145
    DRAEGER = auto()
1✔
146
    TIMPEL = auto()
1✔
147
    SENTEC = auto()
1✔
148
    DRAGER = DRAEGER
1✔
149
    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