PyDPEET 0.2.0__py3-none-any.whl

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 (122) hide show
  1. pydpeet/__init__.py +56 -0
  2. pydpeet/citations/__init__.py +7 -0
  3. pydpeet/citations/citeme.py +397 -0
  4. pydpeet/citations/html_writer.py +137 -0
  5. pydpeet/dev_utils/__init__.py +7 -0
  6. pydpeet/dev_utils/generate_inits/__init__.py +7 -0
  7. pydpeet/dev_utils/generate_inits/generate_inits.py +327 -0
  8. pydpeet/io/__init__.py +7 -0
  9. pydpeet/io/configs/__init__.py +7 -0
  10. pydpeet/io/configs/config.py +227 -0
  11. pydpeet/io/configs/const.py +1 -0
  12. pydpeet/io/convert.py +594 -0
  13. pydpeet/io/device/__init__.py +7 -0
  14. pydpeet/io/device/arbin_4_23_PV090331/__init__.py +7 -0
  15. pydpeet/io/device/arbin_4_23_PV090331/formatter.py +22 -0
  16. pydpeet/io/device/arbin_4_23_PV090331/mapper.py +18 -0
  17. pydpeet/io/device/arbin_4_23_PV090331/reader.py +68 -0
  18. pydpeet/io/device/arbin_8_00_PV221201/__init__.py +7 -0
  19. pydpeet/io/device/arbin_8_00_PV221201/formatter.py +26 -0
  20. pydpeet/io/device/arbin_8_00_PV221201/mapper.py +18 -0
  21. pydpeet/io/device/arbin_8_00_PV221201/reader.py +66 -0
  22. pydpeet/io/device/basytec_6_3_1_0/__init__.py +7 -0
  23. pydpeet/io/device/basytec_6_3_1_0/formatter.py +18 -0
  24. pydpeet/io/device/basytec_6_3_1_0/mapper.py +18 -0
  25. pydpeet/io/device/basytec_6_3_1_0/reader.py +54 -0
  26. pydpeet/io/device/digatron_4_20_6_236/__init__.py +7 -0
  27. pydpeet/io/device/digatron_4_20_6_236/formatter.py +27 -0
  28. pydpeet/io/device/digatron_4_20_6_236/mapper.py +18 -0
  29. pydpeet/io/device/digatron_4_20_6_236/reader.py +45 -0
  30. pydpeet/io/device/digatron_eis_4_20_6_236/__init__.py +7 -0
  31. pydpeet/io/device/digatron_eis_4_20_6_236/formatter.py +39 -0
  32. pydpeet/io/device/digatron_eis_4_20_6_236/mapper.py +18 -0
  33. pydpeet/io/device/digatron_eis_4_20_6_236/reader.py +42 -0
  34. pydpeet/io/device/neware_8_0_0_516/__init__.py +7 -0
  35. pydpeet/io/device/neware_8_0_0_516/formatter.py +31 -0
  36. pydpeet/io/device/neware_8_0_0_516/mapper.py +18 -0
  37. pydpeet/io/device/neware_8_0_0_516/reader.py +397 -0
  38. pydpeet/io/device/parstat_2_63_3/__init__.py +7 -0
  39. pydpeet/io/device/parstat_2_63_3/formatter.py +34 -0
  40. pydpeet/io/device/parstat_2_63_3/mapper.py +18 -0
  41. pydpeet/io/device/parstat_2_63_3/reader.py +34 -0
  42. pydpeet/io/device/safion_1_9/__init__.py +7 -0
  43. pydpeet/io/device/safion_1_9/formatter.py +25 -0
  44. pydpeet/io/device/safion_1_9/mapper.py +18 -0
  45. pydpeet/io/device/safion_1_9/reader.py +89 -0
  46. pydpeet/io/device/zahner/__init__.py +7 -0
  47. pydpeet/io/device/zahner/formatter.py +73 -0
  48. pydpeet/io/device/zahner/mapper.py +34 -0
  49. pydpeet/io/device/zahner/reader.py +46 -0
  50. pydpeet/io/device/zahner_new/__init__.py +7 -0
  51. pydpeet/io/device/zahner_new/formatter.py +72 -0
  52. pydpeet/io/device/zahner_new/mapper.py +50 -0
  53. pydpeet/io/device/zahner_new/reader.py +46 -0
  54. pydpeet/io/map.py +69 -0
  55. pydpeet/io/read.py +50 -0
  56. pydpeet/io/utils/__init__.py +7 -0
  57. pydpeet/io/utils/ext_path.py +34 -0
  58. pydpeet/io/utils/formatter_utils.py +429 -0
  59. pydpeet/io/utils/load_custom_module.py +33 -0
  60. pydpeet/io/utils/timing.py +36 -0
  61. pydpeet/io/write.py +99 -0
  62. pydpeet/process/__init__.py +7 -0
  63. pydpeet/process/analyze/__init__.py +7 -0
  64. pydpeet/process/analyze/average.py +297 -0
  65. pydpeet/process/analyze/capacity.py +208 -0
  66. pydpeet/process/analyze/configs/__init__.py +7 -0
  67. pydpeet/process/analyze/configs/battery_config.py +38 -0
  68. pydpeet/process/analyze/configs/ocv_config.py +177 -0
  69. pydpeet/process/analyze/configs/step_analyzer_config.py +178 -0
  70. pydpeet/process/analyze/cycle.py +63 -0
  71. pydpeet/process/analyze/efficiency.py +182 -0
  72. pydpeet/process/analyze/energy.py +51 -0
  73. pydpeet/process/analyze/extract/__init__.py +7 -0
  74. pydpeet/process/analyze/extract/dva_ica.py +224 -0
  75. pydpeet/process/analyze/extract/ocv.py +196 -0
  76. pydpeet/process/analyze/power.py +32 -0
  77. pydpeet/process/analyze/resistance.py +87 -0
  78. pydpeet/process/analyze/soc.py +418 -0
  79. pydpeet/process/analyze/soh.py +72 -0
  80. pydpeet/process/analyze/utils.py +122 -0
  81. pydpeet/process/merge/__init__.py +7 -0
  82. pydpeet/process/merge/series.py +274 -0
  83. pydpeet/process/sequence/__init__.py +7 -0
  84. pydpeet/process/sequence/configs/__init__.py +7 -0
  85. pydpeet/process/sequence/configs/config.py +17 -0
  86. pydpeet/process/sequence/step_analyzer.py +440 -0
  87. pydpeet/process/sequence/utils/__init__.py +7 -0
  88. pydpeet/process/sequence/utils/annotate/__init__.py +7 -0
  89. pydpeet/process/sequence/utils/annotate/annotate_primitives.py +419 -0
  90. pydpeet/process/sequence/utils/configs/CONFIG_Fallback.py +231 -0
  91. pydpeet/process/sequence/utils/configs/CONFIG_preprocessing.py +243 -0
  92. pydpeet/process/sequence/utils/configs/__init__.py +7 -0
  93. pydpeet/process/sequence/utils/console_prints/__init__.py +7 -0
  94. pydpeet/process/sequence/utils/console_prints/log_time.py +30 -0
  95. pydpeet/process/sequence/utils/postprocessing/__init__.py +7 -0
  96. pydpeet/process/sequence/utils/postprocessing/df_primitives_correction.py +166 -0
  97. pydpeet/process/sequence/utils/postprocessing/filter_df.py +168 -0
  98. pydpeet/process/sequence/utils/postprocessing/generate_instructions.py +297 -0
  99. pydpeet/process/sequence/utils/preprocessing/__init__.py +7 -0
  100. pydpeet/process/sequence/utils/preprocessing/calculate_thresholds.py +27 -0
  101. pydpeet/process/sequence/utils/processing/__init__.py +7 -0
  102. pydpeet/process/sequence/utils/processing/analyze_segments.py +368 -0
  103. pydpeet/process/sequence/utils/processing/attempt_to_merge_neighboring_segments.py +122 -0
  104. pydpeet/process/sequence/utils/processing/check_CV_results.py +226 -0
  105. pydpeet/process/sequence/utils/processing/check_power_zero_watt_segments.py +85 -0
  106. pydpeet/process/sequence/utils/processing/check_zero_length.py +117 -0
  107. pydpeet/process/sequence/utils/processing/split_in_segments.py +97 -0
  108. pydpeet/process/sequence/utils/processing/supress_smaller_segments.py +99 -0
  109. pydpeet/process/sequence/utils/processing/widen_constant_segments.py +185 -0
  110. pydpeet/process/sequence/utils/visualize/__init__.py +7 -0
  111. pydpeet/process/sequence/utils/visualize/visualize_data.py +288 -0
  112. pydpeet/res/__init__.py +7 -0
  113. pydpeet/settings.py +0 -0
  114. pydpeet/utils/__init__.py +7 -0
  115. pydpeet/utils/logging_style.py +28 -0
  116. pydpeet/version.py +0 -0
  117. pydpeet-0.2.0.dist-info/METADATA +85 -0
  118. pydpeet-0.2.0.dist-info/RECORD +122 -0
  119. pydpeet-0.2.0.dist-info/WHEEL +5 -0
  120. pydpeet-0.2.0.dist-info/licenses/AUTHORS.md +0 -0
  121. pydpeet-0.2.0.dist-info/licenses/LICENCE.md +27 -0
  122. pydpeet-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,185 @@
1
+ import logging
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ from numba import njit
6
+
7
+
8
+ # TODO: Typing correct?
9
+ @njit(cache=True)
10
+ def _widen_segments_numba(
11
+ Data_arr: np.ndarray,
12
+ ID_arr: np.ndarray,
13
+ thresholds: np.array,
14
+ ) -> np.array:
15
+ """
16
+ Widen constant segments in a DataFrame by identifying contiguous segments in each column,
17
+ and then widening each segment by extending it to the left and right until the
18
+ average of the segment exceeds the threshold.
19
+
20
+ Parameters:
21
+ Data_arr (numpy array): Input DataFrame with Segment columns.
22
+ ID_arr (numpy array): IDs of the segments in the DataFrame.
23
+ thresholds (numpy array): Threshold values per column.
24
+
25
+ Returns:
26
+ numpy array: The original DataFrame with widened segments.
27
+ """
28
+ n_rows, n_cols = Data_arr.shape
29
+ new_ID_arr = ID_arr.copy()
30
+
31
+ for col_idx in range(n_cols):
32
+ thresh = thresholds[col_idx]
33
+ col_data = Data_arr[:, col_idx]
34
+ col_ids = new_ID_arr[:, col_idx]
35
+
36
+ i = 0
37
+ while i < n_rows:
38
+ col_id = col_ids[i]
39
+ if col_id == -1:
40
+ i += 1
41
+ continue
42
+
43
+ # Identify start and end of contiguous segment
44
+ start = i
45
+ while i + 1 < n_rows and col_ids[i + 1] == col_id:
46
+ i += 1
47
+ end = i
48
+
49
+ # Compute mean and check validity in one pass
50
+ seg_sum = 0.0
51
+ segment_length = end - start + 1
52
+ is_valid = True
53
+ for j in range(start, end + 1):
54
+ seg_sum += col_data[j]
55
+ avg = seg_sum / segment_length
56
+
57
+ for j in range(start, end + 1):
58
+ if abs(col_data[j] - avg) > thresh:
59
+ is_valid = False
60
+ break
61
+ if not is_valid:
62
+ i += 1
63
+ continue
64
+
65
+ # Extend to the right
66
+ extend_r = end
67
+ j = end + 1
68
+ while j < n_rows:
69
+ v = col_data[j]
70
+ cid_j = new_ID_arr[j, col_idx]
71
+ if abs(v - avg) > thresh or (cid_j != -1 and cid_j != col_id):
72
+ break
73
+ extend_r = j
74
+ j += 1
75
+
76
+ # Extend to the left
77
+ extend_l = start
78
+ j = start - 1
79
+ while j >= 0:
80
+ v = col_data[j]
81
+ cid_j = new_ID_arr[j, col_idx]
82
+ if abs(v - avg) > thresh or (cid_j != -1 and cid_j != col_id):
83
+ break
84
+ extend_l = j
85
+ j -= 1
86
+
87
+ # Assign widened ID to extended ranges
88
+ for j in range(extend_l, start):
89
+ new_ID_arr[j, col_idx] = col_id
90
+ for j in range(end + 1, extend_r + 1):
91
+ new_ID_arr[j, col_idx] = col_id
92
+
93
+ # Invalidate other columns only once for the widened areas
94
+ if extend_l < start or extend_r > end:
95
+ for other_col_idx in range(n_cols):
96
+ if other_col_idx == col_idx:
97
+ continue
98
+ for j in range(extend_l, start):
99
+ new_ID_arr[j, other_col_idx] = -1
100
+ for j in range(end + 1, extend_r + 1):
101
+ new_ID_arr[j, other_col_idx] = -1
102
+
103
+ i += 1
104
+
105
+ return new_ID_arr
106
+
107
+
108
+ def _widen_constant_segments(
109
+ df: pd.DataFrame,
110
+ adjust_segments_config: list[tuple[str, float]],
111
+ Threshold_segments_to_print: int,
112
+ supress_IO_warnings: bool,
113
+ ) -> pd.DataFrame:
114
+ """
115
+ Widen constant segments in a DataFrame by identifying contiguous segments in each column,
116
+ and then widening each segment by extending it to the left and right until the
117
+ average of the segment exceeds the threshold.
118
+
119
+ Parameters:
120
+ df (pd.DataFrame): Input DataFrame with Segment columns.
121
+ adjust_segments_config (list of tuples): List of tuples containing column name and threshold value.
122
+ Example: [("Voltage[V]", 0.1), ("Current[A]", 0.1), ("Power[W]", 0.1)]
123
+ Threshold_segments_to_print (int): The maximum number of removed segments to print in the warning message.
124
+ supress_IO_warnings (bool): Whether to suppress warning messages.
125
+
126
+ Returns:
127
+ pd.DataFrame: The original DataFrame with widened segments.
128
+ """
129
+ Data_columns_name_list = [name for name, _ in adjust_segments_config]
130
+ thresholds = np.array([thresh for _, thresh in adjust_segments_config], dtype=np.float64)
131
+ ID_columns_name_list = ["Segment_" + name for name, _ in adjust_segments_config]
132
+
133
+ Data_arr = df[Data_columns_name_list].to_numpy(dtype=np.float64)
134
+ ID_arr = df[ID_columns_name_list].to_numpy(dtype=np.int32)
135
+
136
+ n_rows = ID_arr.shape[0]
137
+
138
+ # Store start and end indices of original segments per column
139
+ original_segments = {col: [] for col in ID_columns_name_list}
140
+ for i, col in enumerate(ID_columns_name_list):
141
+ ids = ID_arr[:, i]
142
+ current_id = ids[0]
143
+ start_idx = 0 if current_id != -1 else None
144
+ for row in range(1, n_rows):
145
+ if ids[row] != current_id:
146
+ if current_id != -1:
147
+ original_segments[col].append((start_idx, row - 1))
148
+ current_id = ids[row]
149
+ start_idx = row if current_id != -1 else None
150
+ if current_id != -1:
151
+ original_segments[col].append((start_idx, n_rows - 1))
152
+
153
+ # Run widening
154
+ new_ID_arr = _widen_segments_numba(Data_arr, ID_arr, thresholds)
155
+
156
+ # Check removed segments and print one-line warning
157
+ for i, col in enumerate(ID_columns_name_list):
158
+ updated_ids = new_ID_arr[:, i]
159
+ removed_ranges = [
160
+ f"{start}:{end}" for start, end in original_segments[col] if np.all(updated_ids[start : end + 1] == -1)
161
+ ]
162
+ if removed_ranges:
163
+ if not supress_IO_warnings:
164
+ if Threshold_segments_to_print:
165
+ if Threshold_segments_to_print < len(removed_ranges):
166
+ logging.warning(
167
+ f"Removed segments during finetuning of the width in '{col}': "
168
+ + ", ".join(removed_ranges[:Threshold_segments_to_print])
169
+ + " ..."
170
+ )
171
+ else:
172
+ logging.warning(
173
+ f"Removed segments during finetuning of the width in '{col}': "
174
+ + ", ".join(removed_ranges[:Threshold_segments_to_print])
175
+ )
176
+ else:
177
+ logging.warning(
178
+ f"Removed segments during finetuning of the width in '{col}': " + ", ".join(removed_ranges)
179
+ )
180
+
181
+ # Update DataFrame
182
+ for i, col in enumerate(ID_columns_name_list):
183
+ df[col] = new_ID_arr[:, i]
184
+
185
+ return df
@@ -0,0 +1,7 @@
1
+ """
2
+ Auto-generated __init__ file.
3
+ Created: 2026-03-06 15:11:49
4
+ """
5
+
6
+ # Restrictive package init: start with no public API
7
+ __all__ = []
@@ -0,0 +1,288 @@
1
+ import logging
2
+ import tkinter as tk
3
+
4
+ import matplotlib.pyplot as plt
5
+ import pandas as pd
6
+
7
+ from pydpeet.process.sequence.utils.console_prints.log_time import log_time
8
+
9
+ # -------------------------------------------------------------------
10
+ # Compute screen dimensions once
11
+ # -------------------------------------------------------------------
12
+ _root = tk.Tk()
13
+ _root.withdraw()
14
+ _root.update_idletasks()
15
+ _SCREEN_DIMS = (_root.winfo_screenwidth(), _root.winfo_screenheight())
16
+ try:
17
+ _root.destroy()
18
+ except tk.TclError:
19
+ pass
20
+
21
+
22
+ def _visualize_phases(
23
+ dataframe: pd.DataFrame,
24
+ start_time: float = 0.0,
25
+ end_time: float = 1e50,
26
+ segment_id_cols: list[str] = None,
27
+ segment_colors: (dict[str, str] | list[str]) = None,
28
+ segment_alpha: float = 0.3,
29
+ columns_to_visualize: list[str] = None,
30
+ line_colors: dict[str, str] = None,
31
+ y_axis_ranges: dict[str, tuple[float, float]] = None,
32
+ use_lines_for_segments: bool = True,
33
+ show_column_names: bool = True,
34
+ show_time: bool = True,
35
+ show_id: bool = True,
36
+ width_height_ratio: list[float, float] = None,
37
+ show_runtime: bool = True,
38
+ show_grid: bool = False,
39
+ ) -> None:
40
+ """
41
+ Visualizes the given dataframe by plotting all columns over time.
42
+
43
+ Parameters:
44
+ dataframe (pd.DataFrame): The dataframe to be visualized.
45
+ start_time (float, optional): The start time of the visualization. Defaults to 0.0.
46
+ end_time (float, optional): The end time of the visualization. Defaults to 1e50.
47
+ segment_id_cols (list[str], optional): The columns of the dataframe to be used as segment IDs. Defaults to None.
48
+ segment_colors (dict[str, str] | list[str], optional): The colors to be used for the segments. Defaults to None.
49
+ segment_alpha (float, optional): The alpha value of the segment backgrounds. Defaults to 0.3.
50
+ columns_to_visualize (list[str], optional): The columns of the dataframe to be visualized. Defaults to ['Voltage[V]', 'Current[A]', 'Power[W]'].
51
+ line_colors (dict[str, str], optional): The colors to be used for the lines. Defaults to None.
52
+ y_axis_ranges (dict[str, tuple[float, float]], optional): The ranges of the y-axis. Defaults to None.
53
+ use_lines_for_segments (bool, optional): Whether to use lines for the segments. Defaults to True.
54
+ show_column_names (bool, optional): Whether to show the column names. Defaults to True.
55
+ show_time (bool, optional): Whether to show the time. Defaults to True.
56
+ show_id (bool, optional): Whether to show the ID. Defaults to True.
57
+ width_height_ratio (list[float, float], optional): The ratio of the width to the height of the figure. Defaults to [1.0, 0.3].
58
+ show_runtime (bool, optional): Whether to show the runtime. Defaults to True.
59
+ show_grid (bool, optional): Whether to show the grid. Defaults to False.
60
+
61
+ Returns:
62
+ None
63
+ """
64
+ # Set default values for mutable data structures
65
+ if columns_to_visualize is None:
66
+ columns_to_visualize = ["Voltage[V]", "Current[A]", "Power[W]"]
67
+ if width_height_ratio is None:
68
+ width_height_ratio = [1.0, 0.3]
69
+
70
+ # 1) Filter by time
71
+ with log_time("filtering by time", show_runtime):
72
+ mask = pd.Series(True, index=dataframe.index)
73
+ if start_time is not None:
74
+ mask &= dataframe["Test_Time[s]"] >= start_time
75
+ if end_time is not None:
76
+ mask &= dataframe["Test_Time[s]"] <= end_time
77
+ df = dataframe.loc[mask]
78
+
79
+ # 2) Normalize line colors and y-axis ranges
80
+ with log_time("normalizing colors and y-axis ranges", show_runtime):
81
+ line_colors = line_colors or {}
82
+ y_axis_ranges = y_axis_ranges or {}
83
+
84
+ # 3) Map segment colors to `Variable` values
85
+ with log_time("mapping segment colors", show_runtime):
86
+ segment_colors = {var: col for var, col in zip(segment_id_cols, segment_colors, strict=False)}
87
+
88
+ # 4) Set up figure
89
+ with log_time("setting up figure", show_runtime):
90
+ screen_w, screen_h = _SCREEN_DIMS
91
+ dpi = plt.rcParams["figure.dpi"]
92
+ fig_w, fig_h = (screen_w * width_height_ratio[0]) / dpi, (screen_h * width_height_ratio[1]) / dpi
93
+
94
+ plt.ioff()
95
+ fig, ax_base = plt.subplots(figsize=(fig_w, fig_h))
96
+ axes = {}
97
+ offset = 0.02
98
+
99
+ for idx, col in enumerate(columns_to_visualize):
100
+ if idx == 0:
101
+ ax = ax_base
102
+ elif idx == 1:
103
+ ax = ax_base.twinx()
104
+ else:
105
+ ax = ax_base.twinx()
106
+ ax.spines["right"].set_position(("axes", 1 + offset))
107
+ ax.spines["right"].set_visible(True)
108
+ ax.yaxis.set_label_position("right")
109
+ ax.yaxis.set_ticks_position("right")
110
+ offset += 0.05
111
+
112
+ ax.set_ylabel(col, color=line_colors.get(col))
113
+ ax.tick_params(axis="y", labelcolor=line_colors.get(col))
114
+ if col in y_axis_ranges:
115
+ ax.set_ylim(*y_axis_ranges[col])
116
+ axes[col] = ax
117
+ fig.subplots_adjust(right=1 + offset + 0.05)
118
+
119
+ # 5) Plot data
120
+ with log_time("plotting data", show_runtime):
121
+ t = df["Test_Time[s]"]
122
+ for col in columns_to_visualize:
123
+ if col in df.columns:
124
+ axes[col].plot(t, df[col], label=col, color=line_colors.get(col))
125
+
126
+ # 6) Group segments by ID + Variable
127
+ with log_time("grouping segments by ID + Variable", show_runtime):
128
+ stats = df.groupby(["ID", "Variable"])["Test_Time[s]"].agg(tmin="min", tmax="max").reset_index()
129
+
130
+ y0, y1 = ax_base.get_ylim()
131
+ height = y1 - y0
132
+
133
+ # 7) Draw segment backgrounds and labels
134
+ with log_time("drawing segment backgrounds and labels", show_runtime):
135
+ intervals = []
136
+ colors = []
137
+ vline_positions = []
138
+ mid_y = (y0 + y1) / 2
139
+
140
+ for _, row in stats.iterrows():
141
+ tmin, tmax = row["tmin"], row["tmax"]
142
+ duration = tmax - tmin
143
+ variable = row["Variable"]
144
+ color = segment_colors.get(variable, "gray")
145
+
146
+ intervals.append((tmin, duration))
147
+ colors.append(color)
148
+
149
+ if use_lines_for_segments:
150
+ vline_positions.append(tmin)
151
+
152
+ # Draw all backgrounds at once
153
+ ax_base.broken_barh(intervals, (y0, height), facecolors=colors, alpha=segment_alpha)
154
+
155
+ # Draw all vertical lines at once
156
+ if use_lines_for_segments and vline_positions:
157
+ ax_base.vlines(vline_positions, y0, y1, colors="k", alpha=segment_alpha)
158
+
159
+ # Draw text labels (still per segment, this is usually okay)
160
+ for _, row in stats.iterrows():
161
+ tmin, tmax = row["tmin"], row["tmax"]
162
+ duration = tmax - tmin
163
+ x_center = tmin + duration / 2
164
+ variable = row["Variable"]
165
+ id = row["ID"]
166
+
167
+ label_parts = []
168
+ if show_id:
169
+ label_parts.append(f"ID:{id}")
170
+ if show_column_names:
171
+ label_parts.append(f"{variable}")
172
+ if show_time:
173
+ label_parts.append(f"{duration:.1f}s")
174
+
175
+ label = " ".join(label_parts)
176
+
177
+ ax_base.text(x_center, mid_y, label, ha="center", va="center", rotation=90, size=10)
178
+
179
+ # 8) Adding grid and legend
180
+ with log_time("adding grid and legend", show_runtime):
181
+ # TODO power labels multiple times shown fix?
182
+ ax_base.set_xlabel("Testtime [s]")
183
+ legend_handles, legend_labels = [], []
184
+ for ax in axes.values():
185
+ handles, labels = ax.get_legend_handles_labels()
186
+ legend_handles += handles
187
+ legend_labels += labels
188
+ if handles:
189
+ ax_base.legend(legend_handles, legend_labels, loc="upper left")
190
+ if show_grid:
191
+ ax_base.grid()
192
+ plt.tight_layout()
193
+
194
+
195
+ # TODO: Docstring
196
+ def visualize_phases(
197
+ dataframe: pd.DataFrame,
198
+ start_time: float = None,
199
+ end_time: float = None,
200
+ visualize_phases_config: list[tuple[str, str]] = None,
201
+ segment_alpha: float = 0.3,
202
+ line_visualization_config: list[tuple[str, str, tuple[float, float]]] = None,
203
+ use_lines_for_segments: bool = True,
204
+ show_column_names: bool = True,
205
+ show_time: bool = True,
206
+ show_id: bool = True,
207
+ width_height_ratio: tuple[float, float] | list[float] = None,
208
+ show_runtime: bool = True,
209
+ ) -> None:
210
+ # Set default values for mutable data structures
211
+ if visualize_phases_config is None:
212
+ visualize_phases_config = [
213
+ ("V", "blue"),
214
+ ("I", "red"),
215
+ ("P", "green"),
216
+ ]
217
+ if line_visualization_config is None:
218
+ line_visualization_config = [
219
+ ("Voltage[V]", "blue", (2.3, 4.3)),
220
+ ("Current[A]", "red", (-10, 10)),
221
+ # ("Power[W]", "green", (-40, 40)),
222
+ ]
223
+ if width_height_ratio is None:
224
+ width_height_ratio: float = [1.0, 0.3]
225
+
226
+ if start_time is None:
227
+ logging.warning("start_time is None - setting it to 0.0")
228
+ start_time = 0.0
229
+ if end_time is None:
230
+ logging.warning("end_time is None - setting it to the biggest possible float")
231
+ end_time = float("inf")
232
+ if dataframe is None:
233
+ raise ValueError("dataframe is None")
234
+ if not isinstance(dataframe, pd.DataFrame):
235
+ raise TypeError("dataframe must be a pandas DataFrame")
236
+ if not (isinstance(width_height_ratio, list | tuple) and len(width_height_ratio) == 2):
237
+ raise ValueError("width_height_ratio must be a list or tuple of length 2")
238
+ if "Test_Time[s]" not in dataframe.columns:
239
+ raise ValueError("dataframe needs to have at least column 'Test_Time[s]'")
240
+ if "ID" not in dataframe.columns:
241
+ raise ValueError("dataframe needs to have at least column 'ID'")
242
+ if not isinstance(visualize_phases_config, list):
243
+ raise TypeError("visualize_phases_config must be a list")
244
+ if not isinstance(line_visualization_config, list):
245
+ raise TypeError("line_visualization_config must be a list")
246
+ if start_time >= end_time:
247
+ raise ValueError(f"start_time ({start_time}) must be less than end_time ({end_time})")
248
+ if not (0 <= segment_alpha <= 1):
249
+ logging.warning("segment_alpha must be between 0 and 1 - resetting it now to 0.3")
250
+ segment_alpha = 0.3
251
+
252
+ if start_time is None:
253
+ start_time = dataframe["Test_Time[s]"].min()
254
+ if end_time is None:
255
+ end_time = dataframe["Test_Time[s]"].max()
256
+
257
+ segment_id_cols = [col for col, _ in visualize_phases_config]
258
+ segment_colors = [color for _, color in visualize_phases_config]
259
+ columns_to_visualize = [col for col, _, _ in line_visualization_config]
260
+ line_colors = {col: color for col, color, _ in line_visualization_config}
261
+ y_axis_ranges = {col: y_range for col, _, y_range in line_visualization_config}
262
+
263
+ if not isinstance(segment_id_cols, list):
264
+ raise TypeError("segment_id_cols must be a list of strings")
265
+ if not isinstance(segment_colors, list | dict):
266
+ raise TypeError("segment_colors must be a list or dict")
267
+ if isinstance(segment_colors, list) and len(segment_id_cols) != len(segment_colors):
268
+ raise ValueError("segment_id_cols and segment_colors must have the same length")
269
+ if not isinstance(columns_to_visualize, list):
270
+ raise TypeError("columns_to_visualize must be a list")
271
+
272
+ _visualize_phases(
273
+ dataframe=dataframe,
274
+ start_time=start_time,
275
+ end_time=end_time,
276
+ segment_id_cols=segment_id_cols,
277
+ segment_colors=segment_colors,
278
+ segment_alpha=segment_alpha,
279
+ columns_to_visualize=columns_to_visualize,
280
+ line_colors=line_colors,
281
+ y_axis_ranges=y_axis_ranges,
282
+ use_lines_for_segments=use_lines_for_segments,
283
+ show_column_names=show_column_names,
284
+ show_time=show_time,
285
+ show_id=show_id,
286
+ width_height_ratio=width_height_ratio,
287
+ show_runtime=show_runtime,
288
+ )
@@ -0,0 +1,7 @@
1
+ """
2
+ Auto-generated __init__ file.
3
+ Created: 2026-03-06 15:11:49
4
+ """
5
+
6
+ # Restrictive package init: start with no public API
7
+ __all__ = []
pydpeet/settings.py ADDED
File without changes
@@ -0,0 +1,7 @@
1
+ """
2
+ Auto-generated __init__ file.
3
+ Created: 2026-03-06 15:11:49
4
+ """
5
+
6
+ # Restrictive package init: start with no public API
7
+ __all__ = []
@@ -0,0 +1,28 @@
1
+ import logging
2
+
3
+
4
+ def set_logging_style(
5
+ level="WARNING", formatting_string="%(levelname)s | %(pathname)s:%(lineno)d | %(message)s"
6
+ ) -> None:
7
+ """
8
+ Sets up on import the logging configuration to use the specified level and a custom format.
9
+
10
+ The logging configuration is set to use the specified level. The format of the
11
+ messages is set to '<levelname> | <pathname>:<lineno> | <message>'.
12
+
13
+ Parameters
14
+ ----------
15
+ level : int, optional
16
+ The logging level to use. Defaults to logging.WARNING.
17
+ formatting_string : str, optional
18
+ The formatting string to use for the log messages. Defaults to
19
+ '%(levelname)s | %(pathname)s:%(lineno)d | %(message)s'.
20
+
21
+ Returns
22
+ -------
23
+ None
24
+ """
25
+ if isinstance(level, str):
26
+ level = getattr(logging, level)
27
+
28
+ logging.basicConfig(level=level, format=formatting_string, force=True)
pydpeet/version.py ADDED
File without changes
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyDPEET
3
+ Version: 0.2.0
4
+ Summary: Python package to read, unify, and convert battery measurement data from arbitrary battery cyclers to Parquet files. This package also provides functions to process, evaluate, and visualise the standardised data.
5
+ Author: Alexander Günter Hinrichsen, Jan Kalisch, Daniel Schröder, Cataldo De Simone
6
+ Author-email: Anton Schlösser <a.schloesser@tu-berlin.de>, Martin Otto <m.otto.1@tu-berlin.de>
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENCE.md
10
+ License-File: AUTHORS.md
11
+ Requires-Dist: pandas
12
+ Requires-Dist: numpy
13
+ Requires-Dist: pyarrow
14
+ Requires-Dist: python-calamine
15
+ Requires-Dist: numba
16
+ Requires-Dist: matplotlib
17
+ Requires-Dist: scipy
18
+ Requires-Dist: bibtexparser
19
+ Requires-Dist: scikit-learn
20
+ Provides-Extra: docs
21
+ Requires-Dist: sphinx; extra == "docs"
22
+ Requires-Dist: pydata-sphinx-theme; extra == "docs"
23
+ Requires-Dist: myst-parser; extra == "docs"
24
+ Requires-Dist: nbsphinx; extra == "docs"
25
+ Requires-Dist: sphinx-copybutton; extra == "docs"
26
+ Requires-Dist: bibtexparser; extra == "docs"
27
+ Dynamic: license-file
28
+
29
+ # PyDPEET - Fast and Easy Battery Data Unification, Processing, and Analysis
30
+
31
+ [[_TOC_]]
32
+
33
+ ## Disclaimer
34
+
35
+ This README is still under (re-)construction due to changes in the codebase.
36
+
37
+ ## Description
38
+
39
+ <!-- This project enables you to convert battery measurement data to a standardized format.
40
+
41
+ Cycler output their measurement data in different formats and different file types, like for example .csv and .xslx. Each has to be handled differently which makes it difficult to work with the data and that's the reason why we created a standardized format.
42
+ The standardized Data and Metadata can be used inside of the code and can be output as a .csv (Data), .xlsx (Data) or parquet(Data) to a output_path of your choosing.
43
+
44
+ Keeping additional data outside of our definition of the standardized columns and custom cycler handling is also possible. -->
45
+
46
+
47
+ ## Standardised Format
48
+
49
+ The standard columns are defined as follows:
50
+
51
+ ```python
52
+ STANDARD_COLUMNS = [
53
+ "Meta_Data",
54
+ "Step_Count",
55
+ "Voltage[V]",
56
+ "Current[A]",
57
+ "Temperature[°C]",
58
+ "Test_Time[s]",
59
+ "Date_Time",
60
+ "EIS_f[Hz]",
61
+ "EIS_Z_Real[Ohm]",
62
+ "EIS_Z_Imag[Ohm]",
63
+ "EIS_DC[A]"
64
+ ]
65
+ ```
66
+
67
+ ## Meta data
68
+
69
+ ## Supported Cyclers
70
+
71
+ ## Installation
72
+
73
+ ### For Developers
74
+
75
+ #### Rebuild package for "pip"
76
+
77
+ #### How to renew the documentation after implementing new functions
78
+
79
+ ### For Users
80
+
81
+ ## Usage
82
+
83
+ ## Custom Handling of Cycler
84
+
85
+ ## How to add a Custom Handling to the Project