shepherd-data 2025.4.1__tar.gz → 2025.5.2__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.
Files changed (28) hide show
  1. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/PKG-INFO +3 -4
  2. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/pyproject.toml +7 -4
  3. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data/__init__.py +1 -1
  4. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data/cli.py +5 -6
  5. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data/ivonne.py +13 -13
  6. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data/mppt.py +3 -3
  7. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data/reader.py +63 -45
  8. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data.egg-info/PKG-INFO +3 -4
  9. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data.egg-info/requires.txt +1 -1
  10. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_ivonne.py +3 -3
  11. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/README.md +0 -0
  12. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/setup.cfg +0 -0
  13. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data.egg-info/SOURCES.txt +0 -0
  14. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data.egg-info/dependency_links.txt +0 -0
  15. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data.egg-info/entry_points.txt +0 -0
  16. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data.egg-info/top_level.txt +0 -0
  17. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/shepherd_data.egg-info/zip-safe +0 -0
  18. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_cli.py +0 -0
  19. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_cli_downsample.py +0 -0
  20. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_cli_extract.py +0 -0
  21. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_cli_extract_gpio.py +0 -0
  22. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_cli_extract_meta.py +0 -0
  23. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_cli_extract_uart.py +0 -0
  24. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_cli_plot.py +0 -0
  25. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_cli_validate.py +0 -0
  26. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_cli_version.py +0 -0
  27. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_examples.py +0 -0
  28. {shepherd_data-2025.4.1 → shepherd_data-2025.5.2}/tests/test_reader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shepherd_data
3
- Version: 2025.4.1
3
+ Version: 2025.5.2
4
4
  Summary: Programming- and CLI-Interface for the h5-dataformat of the Shepherd-Testbed
5
5
  Author-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
6
6
  Maintainer-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
@@ -18,7 +18,6 @@ Classifier: Development Status :: 5 - Production/Stable
18
18
  Classifier: Intended Audience :: Developers
19
19
  Classifier: Intended Audience :: Information Technology
20
20
  Classifier: Intended Audience :: Science/Research
21
- Classifier: Programming Language :: Python :: 3.8
22
21
  Classifier: Programming Language :: Python :: 3.9
23
22
  Classifier: Programming Language :: Python :: 3.10
24
23
  Classifier: Programming Language :: Python :: 3.11
@@ -27,7 +26,7 @@ Classifier: Programming Language :: Python :: 3.13
27
26
  Classifier: License :: OSI Approved :: MIT License
28
27
  Classifier: Operating System :: OS Independent
29
28
  Classifier: Natural Language :: English
30
- Requires-Python: >=3.8
29
+ Requires-Python: >=3.9
31
30
  Description-Content-Type: text/markdown
32
31
  Requires-Dist: click
33
32
  Requires-Dist: h5py
@@ -36,7 +35,7 @@ Requires-Dist: numpy
36
35
  Requires-Dist: pandas>=2.0.0
37
36
  Requires-Dist: pyYAML
38
37
  Requires-Dist: scipy
39
- Requires-Dist: shepherd-core[inventory]>=2025.04.1
38
+ Requires-Dist: shepherd-core[inventory]>=2025.05.2
40
39
  Requires-Dist: tqdm
41
40
  Provides-Extra: elf
42
41
  Requires-Dist: shepherd-core[elf]; extra == "elf"
@@ -18,7 +18,6 @@ classifiers = [
18
18
  "Intended Audience :: Developers",
19
19
  "Intended Audience :: Information Technology",
20
20
  "Intended Audience :: Science/Research",
21
- "Programming Language :: Python :: 3.8",
22
21
  "Programming Language :: Python :: 3.9",
23
22
  "Programming Language :: Python :: 3.10",
24
23
  "Programming Language :: Python :: 3.11",
@@ -29,7 +28,7 @@ classifiers = [
29
28
  "Natural Language :: English",
30
29
  ]
31
30
 
32
- requires-python = ">=3.8"
31
+ requires-python = ">=3.9"
33
32
  dependencies = [
34
33
  "click",
35
34
  "h5py",
@@ -38,7 +37,7 @@ dependencies = [
38
37
  "pandas>=2.0.0", # full-version, v2 is OK
39
38
  "pyYAML",
40
39
  "scipy", # full-version
41
- "shepherd-core[inventory]>=2025.04.1", # libs are strongly coupled
40
+ "shepherd-core[inventory]>=2025.05.2", # libs are strongly coupled
42
41
  "tqdm", # full-version
43
42
  ]
44
43
 
@@ -101,6 +100,10 @@ addopts = "-vvv --stepwise" # opts: verbose result for each tests
101
100
  source = ["shepherd_data"]
102
101
 
103
102
  [tool.mypy]
104
- python_version = 3.8
103
+ python_version = 3.9
105
104
  ignore_missing_imports = true
106
105
  disable_error_code = ["call-arg", ]
106
+ exclude = [
107
+ "build/",
108
+ ".egg-info/",
109
+ ]
@@ -11,7 +11,7 @@ from shepherd_core import Writer
11
11
 
12
12
  from .reader import Reader
13
13
 
14
- __version__ = "2025.04.1"
14
+ __version__ = "2025.05.2"
15
15
 
16
16
  __all__ = [
17
17
  "Reader",
@@ -5,7 +5,6 @@ import sys
5
5
  from contextlib import suppress
6
6
  from datetime import datetime
7
7
  from pathlib import Path
8
- from typing import List
9
8
  from typing import Optional
10
9
 
11
10
  import click
@@ -21,7 +20,7 @@ from .reader import Reader
21
20
  logger = logging.getLogger("SHPData.cli")
22
21
 
23
22
 
24
- def path_to_flist(data_path: Path, *, recurse: bool = False) -> List[Path]:
23
+ def path_to_flist(data_path: Path, *, recurse: bool = False) -> list[Path]:
25
24
  """Every path gets transformed to a list of paths.
26
25
 
27
26
  Transformations:
@@ -93,7 +92,7 @@ def validate(in_data: Path, *, recurse: bool = False) -> None:
93
92
  sys.exit(int(not valid_dir))
94
93
 
95
94
 
96
- @cli.command(short_help="Extracts recorded IVSamples and stores it to csv")
95
+ @cli.command(short_help="Extracts recorded IVTrace and stores it to csv")
97
96
  @click.argument("in_data", type=click.Path(exists=True, resolve_path=True))
98
97
  @click.option(
99
98
  "--start",
@@ -144,7 +143,7 @@ def extract(
144
143
  raw: bool = False,
145
144
  recurse: bool = False,
146
145
  ) -> None:
147
- """Extract recorded IVSamples and store them to csv."""
146
+ """Extract recorded IVTrace and store them to csv."""
148
147
  files = path_to_flist(in_data, recurse=recurse)
149
148
  verbose_level = get_verbose_level()
150
149
  if not isinstance(ds_factor, (float, int)) or ds_factor < 1:
@@ -190,12 +189,12 @@ def extract_meta(in_data: Path, separator: str, *, recurse: bool = False) -> Non
190
189
  with Reader(file, verbose=verbose_level > 2) as shpr:
191
190
  shpr.save_metadata()
192
191
  csvs_depr = ["sysutil", "timesync"]
193
- csvs = ["ptp", "sys_util", "pru_util"]
192
+ csvs = ["ptp", "phc2sys", "sys_util", "pru_util"]
194
193
  for element in csvs + csvs_depr:
195
194
  if element in shpr.h5file:
196
195
  shpr.save_csv(shpr[element], separator)
197
196
  logs_depr = ["shepherd-log", "dmesg", "exceptions"]
198
- logs = ["sheep", "kernel", "phc2sys", "uart"]
197
+ logs = ["sheep", "kernel", "ntp", "uart"]
199
198
  for element in logs + logs_depr:
200
199
  if element in shpr.h5file:
201
200
  shpr.save_log(shpr[element])
@@ -17,14 +17,14 @@ import pickle
17
17
  from pathlib import Path
18
18
  from types import TracebackType
19
19
  from typing import Optional
20
- from typing import Type
21
20
 
22
21
  import numpy as np
23
22
  import pandas as pd
24
23
  from tqdm import trange
25
24
  from typing_extensions import Self
26
25
 
27
- from . import Writer
26
+ from shepherd_core.writer import Writer as CoreWriter
27
+
28
28
  from .mppt import MPPTracker
29
29
  from .mppt import OptimalTracker
30
30
  from .mppt import iv_model
@@ -88,7 +88,7 @@ class Reader:
88
88
 
89
89
  def __exit__(
90
90
  self,
91
- typ: Optional[Type[BaseException]] = None,
91
+ typ: Optional[type[BaseException]] = None,
92
92
  exc: Optional[BaseException] = None,
93
93
  tb: Optional[TracebackType] = None,
94
94
  extra_arg: int = 0,
@@ -102,7 +102,7 @@ class Reader:
102
102
  self.file_size = self.file_path.stat().st_size
103
103
  self.data_rate = self.file_size / self.runtime_s if self.runtime_s > 0 else 0
104
104
 
105
- def convert_2_ivcurves(
105
+ def convert_2_ivsurface(
106
106
  self,
107
107
  shp_output: Path,
108
108
  v_max: float = 5.0,
@@ -132,12 +132,14 @@ class Reader:
132
132
 
133
133
  v_proto = np.linspace(0, v_max, pts_per_curve)
134
134
 
135
- with Writer(shp_output, datatype="ivcurve", window_samples=pts_per_curve) as sfw:
135
+ with CoreWriter(shp_output, datatype="ivcurve", window_samples=pts_per_curve) as sfw:
136
136
  sfw.store_hostname("IVonne")
137
137
  curve_interval_us = round(sfw.sample_interval_ns * pts_per_curve / 1000)
138
138
  up_factor = self.sample_interval_ns // sfw.sample_interval_ns
139
139
  max_elements = math.ceil(sfw.max_elements // up_factor)
140
- job_iter = trange(0, df_elements_n, max_elements, desc="generate ivcurves", leave=False)
140
+ job_iter = trange(
141
+ 0, df_elements_n, max_elements, desc="generate ivsurface", leave=False
142
+ )
141
143
 
142
144
  for idx in job_iter:
143
145
  idx_top = min(idx + max_elements, df_elements_n)
@@ -164,14 +166,14 @@ class Reader:
164
166
  # - time can be generated and set as a whole
165
167
  # - v_proto is repetitive, can also be set as a whole
166
168
 
167
- def convert_2_ivsamples(
169
+ def convert_2_ivtrace(
168
170
  self,
169
171
  shp_output: Path,
170
172
  v_max: float = 5.0,
171
173
  duration_s: Optional[float] = None,
172
174
  tracker: Optional[MPPTracker] = None,
173
175
  ) -> None:
174
- """Transform shepherd IV curves to shepherd IV samples / traces.
176
+ """Transform shepherd IV surface / curves to shepherd IV trace / samples .
175
177
 
176
178
  For the 'buck' and 'buck-boost' modes, shepherd takes voltage and current traces.
177
179
  These can be recorded with shepherd or generated from existing IV curves by, for
@@ -207,14 +209,12 @@ class Reader:
207
209
  v_max,
208
210
  )
209
211
 
210
- with Writer(shp_output, datatype="ivsample") as sfw:
212
+ with CoreWriter(shp_output, datatype="ivsample") as sfw:
211
213
  sfw.store_hostname("IVonne")
212
214
  interval_us = round(sfw.sample_interval_ns / 1000)
213
215
  up_factor = self.sample_interval_ns // sfw.sample_interval_ns
214
216
  max_elements = math.ceil(sfw.max_elements // up_factor)
215
- job_iter = trange(
216
- 0, df_elements_n, max_elements, desc="generate ivsamples", leave=False
217
- )
217
+ job_iter = trange(0, df_elements_n, max_elements, desc="generate ivtrace", leave=False)
218
218
 
219
219
  for idx in job_iter:
220
220
  # select (max_elements + 1) elements, so upsampling is without gaps
@@ -260,7 +260,7 @@ class Reader:
260
260
  self._logger.info("File already exists, will skip '%s'", shp_output.name)
261
261
  return
262
262
 
263
- with Writer(shp_output, datatype="isc_voc") as sfw:
263
+ with CoreWriter(shp_output, datatype="isc_voc") as sfw:
264
264
  sfw.store_hostname("IVonne")
265
265
  interval_us = round(sfw.sample_interval_ns / 1000)
266
266
  up_factor = self.sample_interval_ns // sfw.sample_interval_ns
@@ -56,7 +56,7 @@ class MPPTracker(ABC):
56
56
  """Apply harvesting model to input data.
57
57
 
58
58
  :param coeffs: ivonne coefficients
59
- :return: ivsample-data
59
+ :return: ivtrace-data
60
60
  """
61
61
 
62
62
 
@@ -76,7 +76,7 @@ class OpenCircuitTracker(MPPTracker):
76
76
  """Apply harvesting model to input data.
77
77
 
78
78
  :param coeffs: ivonne coefficients
79
- :return: ivsample-data
79
+ :return: ivtrace-data
80
80
  """
81
81
  coeffs["icurve"] = coeffs.apply(lambda x: iv_model(self.v_proto, x), axis=1)
82
82
  if "voc" not in coeffs.columns:
@@ -106,7 +106,7 @@ class OptimalTracker(MPPTracker):
106
106
  """Apply harvesting model to input data.
107
107
 
108
108
  :param coeffs: ivonne coefficients
109
- :return: ivsample-data
109
+ :return: ivtrace-data
110
110
  """
111
111
  coeffs["icurve"] = coeffs.apply(lambda x: iv_model(self.v_proto, x), axis=1)
112
112
  coeffs["pcurve"] = coeffs.apply(lambda x: self.v_proto * x["icurve"], axis=1)
@@ -1,13 +1,11 @@
1
1
  """Reader-Baseclass for opening shepherds hdf5-files."""
2
2
 
3
3
  import math
4
+ from collections.abc import Mapping
5
+ from collections.abc import Sequence
4
6
  from datetime import datetime
5
7
  from pathlib import Path
6
- from typing import Dict
7
- from typing import List
8
- from typing import Mapping
9
8
  from typing import Optional
10
- from typing import Sequence
11
9
  from typing import Union
12
10
 
13
11
  import h5py
@@ -18,6 +16,7 @@ from tqdm import trange
18
16
  from shepherd_core import Reader as CoreReader
19
17
  from shepherd_core import Writer as CoreWriter
20
18
  from shepherd_core import local_tz
19
+ from shepherd_core.data_models import EnergyDType
21
20
  from shepherd_core.logger import get_verbose_level
22
21
  from shepherd_core.logger import logger
23
22
 
@@ -36,9 +35,9 @@ class Reader(CoreReader):
36
35
 
37
36
  def __init__(
38
37
  self,
39
- file_path: Optional[Path],
38
+ file_path: Path,
40
39
  *,
41
- verbose: Optional[bool] = True,
40
+ verbose: bool = True,
42
41
  ) -> None:
43
42
  super().__init__(file_path, verbose=verbose)
44
43
 
@@ -59,13 +58,13 @@ class Reader(CoreReader):
59
58
  if csv_path.exists():
60
59
  self._logger.info("File already exists, will skip '%s'", csv_path.name)
61
60
  return 0
62
- datasets: List[str] = [
61
+ datasets: list[str] = [
63
62
  str(key) for key in h5_group if isinstance(h5_group[key], h5py.Dataset)
64
63
  ]
65
64
  datasets.remove("time")
66
65
  datasets = ["time", *datasets]
67
66
  separator = separator.strip().ljust(2)
68
- header_elements: List[str] = [
67
+ header_elements: list[str] = [
69
68
  str(h5_group[key].attrs["description"]).replace(", ", separator) for key in datasets
70
69
  ]
71
70
  header: str = separator.join(header_elements)
@@ -74,10 +73,10 @@ class Reader(CoreReader):
74
73
  csv_file.write(header + "\n")
75
74
  ts_gain = h5_group["time"].attrs.get("gain", 1e-9)
76
75
  # for converting data to si - if raw=false
77
- gains: Dict[str, float] = {
76
+ gains: dict[str, float] = {
78
77
  key: h5_group[key].attrs.get("gain", 1.0) for key in datasets[1:]
79
78
  }
80
- offsets: Dict[str, float] = {
79
+ offsets: dict[str, float] = {
81
80
  key: h5_group[key].attrs.get("offset", 1.0) for key in datasets[1:]
82
81
  }
83
82
  for idx, time_ns in enumerate(h5_group["time"][:]):
@@ -94,7 +93,7 @@ class Reader(CoreReader):
94
93
  return h5_group["time"][:].shape[0]
95
94
 
96
95
  def save_log(self, h5_group: h5py.Group, *, add_timestamp: bool = True) -> int:
97
- """Save dataset from group as log, optimal for logged 'dmesg' and console-output.
96
+ """Save dataset from groups as log, optimal for logged kernel- and console-output.
98
97
 
99
98
  :param h5_group: can be external
100
99
  :param add_timestamp: can be external
@@ -177,7 +176,8 @@ class Reader(CoreReader):
177
176
  ) -> Union[None, h5py.Dataset, np.ndarray]:
178
177
  """Sample down iv-data.
179
178
 
180
- Warning: only valid for IV-Stream, not IV-Curves
179
+ Warning: only valid for IV-Stream, not IV-Curves,
180
+ TODO: globally rename to IVTrace, IVSurface
181
181
 
182
182
  :param data_src: a h5-dataset to digest, can be external
183
183
  :param data_dst: can be a dataset, numpy-array or None (will be created internally then)
@@ -189,8 +189,8 @@ class Reader(CoreReader):
189
189
  """
190
190
  from scipy import signal # here due to massive delay
191
191
 
192
- if self.get_datatype() == "ivcurve":
193
- self._logger.warning("Downsampling-Function was not written for IVCurves")
192
+ if self.get_datatype() == EnergyDType.ivsurface:
193
+ self._logger.warning("Downsampling-Function was not written for IVSurfaces")
194
194
  ds_factor = max(1, math.floor(ds_factor))
195
195
 
196
196
  if isinstance(end_n, (int, float)):
@@ -203,9 +203,9 @@ class Reader(CoreReader):
203
203
  if data_len == 0:
204
204
  self._logger.warning("downsampling failed because of data_len = 0")
205
205
  return data_dst
206
- iblock_len = min(self.max_elements, data_len)
207
- oblock_len = round(iblock_len / ds_factor)
208
- iterations = math.ceil(data_len / iblock_len)
206
+ chunk_size_inp = min(self.max_elements, data_len)
207
+ chunk_size_out = round(chunk_size_inp / ds_factor)
208
+ iterations = math.ceil(data_len / chunk_size_inp)
209
209
  dest_len = math.floor(data_len / ds_factor)
210
210
  if data_dst is None:
211
211
  data_dst = np.empty((dest_len,))
@@ -224,8 +224,13 @@ class Reader(CoreReader):
224
224
  )
225
225
  # filter state - needed for sliced calculation
226
226
  f_state = np.zeros((filter_.shape[0], 2))
227
+ # prime the state to avoid starting from 0
228
+ if not is_time and ds_factor > 1:
229
+ slice_ds = data_src[start_n : start_n + self.CHUNK_SAMPLES_N]
230
+ slice_ds[:] = slice_ds[:].mean()
231
+ slice_ds, f_state = signal.sosfilt(filter_, slice_ds, zi=f_state)
227
232
 
228
- slice_len = 0
233
+ output_pos = 0
229
234
  for _iter in trange(
230
235
  0,
231
236
  iterations,
@@ -233,16 +238,22 @@ class Reader(CoreReader):
233
238
  leave=False,
234
239
  disable=iterations < 8,
235
240
  ):
236
- slice_ds = data_src[start_n + _iter * iblock_len : start_n + (_iter + 1) * iblock_len]
241
+ slice_ds = data_src[
242
+ start_n + _iter * chunk_size_inp : start_n + (_iter + 1) * chunk_size_inp
243
+ ]
237
244
  if not is_time and ds_factor > 1:
238
245
  slice_ds, f_state = signal.sosfilt(filter_, slice_ds, zi=f_state)
239
246
  slice_ds = slice_ds[::ds_factor]
240
- slice_len = min(dest_len - _iter * oblock_len, oblock_len)
241
- data_dst[_iter * oblock_len : (_iter + 1) * oblock_len] = slice_ds[:slice_len]
247
+ slice_len = min(dest_len - _iter * chunk_size_out, chunk_size_out, len(slice_ds))
248
+ data_dst[output_pos : output_pos + slice_len] = slice_ds[:slice_len]
249
+ # workaround to allow processing last slice (often smaller than expected),
250
+ # wanted: [_iter * chunk_size_out : (_iter + 1) * chunk_size_out]
251
+ # this prevents future parallel processing!
252
+ output_pos += slice_len
242
253
  if isinstance(data_dst, np.ndarray):
243
- data_dst.resize((oblock_len * (iterations - 1) + slice_len,), refcheck=False)
254
+ data_dst.resize((output_pos,), refcheck=False)
244
255
  else:
245
- data_dst.resize((oblock_len * (iterations - 1) + slice_len,))
256
+ data_dst.resize((output_pos,))
246
257
  return data_dst
247
258
 
248
259
  def cut_and_downsample_to_file(
@@ -271,24 +282,20 @@ class Reader(CoreReader):
271
282
 
272
283
  # test input-parameters
273
284
  if end_sample < start_sample:
274
- raise ValueError(
275
- "Cut & downsample for %s failed because "
276
- "end-mark (%.3f) is before start-mark (%.3f).",
277
- self.file_path.name,
278
- end_s,
279
- start_s,
285
+ msg = (
286
+ f"Cut & downsample for {self.file_path.name} failed because "
287
+ f"end-mark ({end_s:.3f}) is before start-mark ({start_s:.3f})."
280
288
  )
289
+ raise ValueError(msg)
281
290
  if ds_factor < 1:
282
- raise ValueError(
283
- "Cut & downsample for %s failed because factor < 1",
284
- self.file_path.name,
285
- )
291
+ msg = f"Cut & downsample for {self.file_path.name} failed because factor < 1"
292
+ raise ValueError(msg)
286
293
  if ((end_sample - start_sample) / ds_factor) < 1000:
287
- raise ValueError(
288
- "Cut & downsample for %s failed because resulting sample-size is too small",
289
- self.file_path.name,
294
+ msg = (
295
+ f"Cut & downsample for {self.file_path.name} failed because "
296
+ f"resulting sample-size is too small",
290
297
  )
291
-
298
+ raise ValueError(msg)
292
299
  # assemble file-name of output
293
300
  if start_s != 0.0 or end_s != self.runtime_s:
294
301
  start_str = f"{start_s:.3f}".replace(".", "s")
@@ -372,8 +379,8 @@ class Reader(CoreReader):
372
379
  :return: resampled iv-data
373
380
  """
374
381
  self._logger.error("Resampling is still under construction - do not use for now!")
375
- if self.get_datatype() == "ivcurve":
376
- self._logger.warning("Resampling-Function was not written for IVCurves")
382
+ if self.get_datatype() == EnergyDType.ivsurface:
383
+ self._logger.warning("Resampling-Function was not written for IVSurfaces")
377
384
  return data_dst
378
385
  if isinstance(end_n, (int, float)):
379
386
  _end_n = min(data_src.shape[0], round(end_n))
@@ -460,7 +467,7 @@ class Reader(CoreReader):
460
467
  end_s: Optional[float] = None,
461
468
  *,
462
469
  relative_timestamp: bool = True,
463
- ) -> Optional[Dict]:
470
+ ) -> Optional[dict]:
464
471
  """Provide down-sampled iv-data that can be fed into plot_to_file().
465
472
 
466
473
  :param start_s: time in seconds, relative to start of recording
@@ -468,8 +475,8 @@ class Reader(CoreReader):
468
475
  :param relative_timestamp: treat
469
476
  :return: down-sampled size of ~ self.max_elements
470
477
  """
471
- if self.get_datatype() == "ivcurve":
472
- self._logger.warning("Plot-Function was not written for IVCurves.")
478
+ if self.get_datatype() == EnergyDType.ivsurface:
479
+ self._logger.warning("Plot-Function was not written for IVSurfaces.")
473
480
  if not isinstance(start_s, (float, int)):
474
481
  start_s = 0
475
482
  if not isinstance(end_s, (float, int)):
@@ -538,11 +545,20 @@ class Reader(CoreReader):
538
545
  # last axis is set below
539
546
 
540
547
  for date in data:
548
+ samples_n = min(len(date["time"]), len(date["voltage"]), len(date["current"]))
541
549
  if not only_pwr:
542
- axs[0].plot(date["time"], date["voltage"], label=date["name"])
543
- axs[1].plot(date["time"], date["current"] * 10**3, label=date["name"])
550
+ axs[0].plot(
551
+ date["time"][:samples_n], date["voltage"][:samples_n], label=date["name"]
552
+ )
553
+ axs[1].plot(
554
+ date["time"][:samples_n],
555
+ date["current"][:samples_n] * 10**3,
556
+ label=date["name"],
557
+ )
544
558
  axs[-1].plot(
545
- date["time"], date["voltage"] * date["current"] * 10**3, label=date["name"]
559
+ date["time"][:samples_n],
560
+ date["voltage"][:samples_n] * date["current"][:samples_n] * 10**3,
561
+ label=date["name"],
546
562
  )
547
563
 
548
564
  if len(data) > 1:
@@ -553,6 +569,8 @@ class Reader(CoreReader):
553
569
  # deactivates offset-creation for ax-ticks
554
570
  ax.get_yaxis().get_major_formatter().set_useOffset(False)
555
571
  ax.get_xaxis().get_major_formatter().set_useOffset(False)
572
+ # add a thin and light gray grid, TODO: add option to switch off?
573
+ ax.grid(color="0.8", linewidth=0.5)
556
574
  return fig
557
575
 
558
576
  def plot_to_file(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shepherd_data
3
- Version: 2025.4.1
3
+ Version: 2025.5.2
4
4
  Summary: Programming- and CLI-Interface for the h5-dataformat of the Shepherd-Testbed
5
5
  Author-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
6
6
  Maintainer-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
@@ -18,7 +18,6 @@ Classifier: Development Status :: 5 - Production/Stable
18
18
  Classifier: Intended Audience :: Developers
19
19
  Classifier: Intended Audience :: Information Technology
20
20
  Classifier: Intended Audience :: Science/Research
21
- Classifier: Programming Language :: Python :: 3.8
22
21
  Classifier: Programming Language :: Python :: 3.9
23
22
  Classifier: Programming Language :: Python :: 3.10
24
23
  Classifier: Programming Language :: Python :: 3.11
@@ -27,7 +26,7 @@ Classifier: Programming Language :: Python :: 3.13
27
26
  Classifier: License :: OSI Approved :: MIT License
28
27
  Classifier: Operating System :: OS Independent
29
28
  Classifier: Natural Language :: English
30
- Requires-Python: >=3.8
29
+ Requires-Python: >=3.9
31
30
  Description-Content-Type: text/markdown
32
31
  Requires-Dist: click
33
32
  Requires-Dist: h5py
@@ -36,7 +35,7 @@ Requires-Dist: numpy
36
35
  Requires-Dist: pandas>=2.0.0
37
36
  Requires-Dist: pyYAML
38
37
  Requires-Dist: scipy
39
- Requires-Dist: shepherd-core[inventory]>=2025.04.1
38
+ Requires-Dist: shepherd-core[inventory]>=2025.05.2
40
39
  Requires-Dist: tqdm
41
40
  Provides-Extra: elf
42
41
  Requires-Dist: shepherd-core[elf]; extra == "elf"
@@ -5,7 +5,7 @@ numpy
5
5
  pandas>=2.0.0
6
6
  pyYAML
7
7
  scipy
8
- shepherd-core[inventory]>=2025.04.1
8
+ shepherd-core[inventory]>=2025.05.2
9
9
  tqdm
10
10
 
11
11
  [dev]
@@ -23,13 +23,13 @@ def test_convert_ivonne(tmp_path: Path, example_path: Path) -> None:
23
23
 
24
24
  with ivonne.Reader(inp_path) as ifr:
25
25
  ifr.upsample_2_isc_voc(isc_path, duration_s=20)
26
- ifr.convert_2_ivcurves(ivc_path, duration_s=20)
26
+ ifr.convert_2_ivsurface(ivc_path, duration_s=20)
27
27
 
28
28
  tr_voc = mppt.OpenCircuitTracker(ratio=0.76)
29
29
  tr_opt = mppt.OptimalTracker()
30
30
 
31
- ifr.convert_2_ivsamples(voc_path, tracker=tr_voc, duration_s=20)
32
- ifr.convert_2_ivsamples(opt_path, tracker=tr_opt, duration_s=20)
31
+ ifr.convert_2_ivtrace(voc_path, tracker=tr_voc, duration_s=20)
32
+ ifr.convert_2_ivtrace(opt_path, tracker=tr_opt, duration_s=20)
33
33
 
34
34
  energies = {}
35
35
  for file_path in [isc_path, ivc_path, voc_path, opt_path]: