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.
- pydpeet/__init__.py +56 -0
- pydpeet/citations/__init__.py +7 -0
- pydpeet/citations/citeme.py +397 -0
- pydpeet/citations/html_writer.py +137 -0
- pydpeet/dev_utils/__init__.py +7 -0
- pydpeet/dev_utils/generate_inits/__init__.py +7 -0
- pydpeet/dev_utils/generate_inits/generate_inits.py +327 -0
- pydpeet/io/__init__.py +7 -0
- pydpeet/io/configs/__init__.py +7 -0
- pydpeet/io/configs/config.py +227 -0
- pydpeet/io/configs/const.py +1 -0
- pydpeet/io/convert.py +594 -0
- pydpeet/io/device/__init__.py +7 -0
- pydpeet/io/device/arbin_4_23_PV090331/__init__.py +7 -0
- pydpeet/io/device/arbin_4_23_PV090331/formatter.py +22 -0
- pydpeet/io/device/arbin_4_23_PV090331/mapper.py +18 -0
- pydpeet/io/device/arbin_4_23_PV090331/reader.py +68 -0
- pydpeet/io/device/arbin_8_00_PV221201/__init__.py +7 -0
- pydpeet/io/device/arbin_8_00_PV221201/formatter.py +26 -0
- pydpeet/io/device/arbin_8_00_PV221201/mapper.py +18 -0
- pydpeet/io/device/arbin_8_00_PV221201/reader.py +66 -0
- pydpeet/io/device/basytec_6_3_1_0/__init__.py +7 -0
- pydpeet/io/device/basytec_6_3_1_0/formatter.py +18 -0
- pydpeet/io/device/basytec_6_3_1_0/mapper.py +18 -0
- pydpeet/io/device/basytec_6_3_1_0/reader.py +54 -0
- pydpeet/io/device/digatron_4_20_6_236/__init__.py +7 -0
- pydpeet/io/device/digatron_4_20_6_236/formatter.py +27 -0
- pydpeet/io/device/digatron_4_20_6_236/mapper.py +18 -0
- pydpeet/io/device/digatron_4_20_6_236/reader.py +45 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/__init__.py +7 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/formatter.py +39 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/mapper.py +18 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/reader.py +42 -0
- pydpeet/io/device/neware_8_0_0_516/__init__.py +7 -0
- pydpeet/io/device/neware_8_0_0_516/formatter.py +31 -0
- pydpeet/io/device/neware_8_0_0_516/mapper.py +18 -0
- pydpeet/io/device/neware_8_0_0_516/reader.py +397 -0
- pydpeet/io/device/parstat_2_63_3/__init__.py +7 -0
- pydpeet/io/device/parstat_2_63_3/formatter.py +34 -0
- pydpeet/io/device/parstat_2_63_3/mapper.py +18 -0
- pydpeet/io/device/parstat_2_63_3/reader.py +34 -0
- pydpeet/io/device/safion_1_9/__init__.py +7 -0
- pydpeet/io/device/safion_1_9/formatter.py +25 -0
- pydpeet/io/device/safion_1_9/mapper.py +18 -0
- pydpeet/io/device/safion_1_9/reader.py +89 -0
- pydpeet/io/device/zahner/__init__.py +7 -0
- pydpeet/io/device/zahner/formatter.py +73 -0
- pydpeet/io/device/zahner/mapper.py +34 -0
- pydpeet/io/device/zahner/reader.py +46 -0
- pydpeet/io/device/zahner_new/__init__.py +7 -0
- pydpeet/io/device/zahner_new/formatter.py +72 -0
- pydpeet/io/device/zahner_new/mapper.py +50 -0
- pydpeet/io/device/zahner_new/reader.py +46 -0
- pydpeet/io/map.py +69 -0
- pydpeet/io/read.py +50 -0
- pydpeet/io/utils/__init__.py +7 -0
- pydpeet/io/utils/ext_path.py +34 -0
- pydpeet/io/utils/formatter_utils.py +429 -0
- pydpeet/io/utils/load_custom_module.py +33 -0
- pydpeet/io/utils/timing.py +36 -0
- pydpeet/io/write.py +99 -0
- pydpeet/process/__init__.py +7 -0
- pydpeet/process/analyze/__init__.py +7 -0
- pydpeet/process/analyze/average.py +297 -0
- pydpeet/process/analyze/capacity.py +208 -0
- pydpeet/process/analyze/configs/__init__.py +7 -0
- pydpeet/process/analyze/configs/battery_config.py +38 -0
- pydpeet/process/analyze/configs/ocv_config.py +177 -0
- pydpeet/process/analyze/configs/step_analyzer_config.py +178 -0
- pydpeet/process/analyze/cycle.py +63 -0
- pydpeet/process/analyze/efficiency.py +182 -0
- pydpeet/process/analyze/energy.py +51 -0
- pydpeet/process/analyze/extract/__init__.py +7 -0
- pydpeet/process/analyze/extract/dva_ica.py +224 -0
- pydpeet/process/analyze/extract/ocv.py +196 -0
- pydpeet/process/analyze/power.py +32 -0
- pydpeet/process/analyze/resistance.py +87 -0
- pydpeet/process/analyze/soc.py +418 -0
- pydpeet/process/analyze/soh.py +72 -0
- pydpeet/process/analyze/utils.py +122 -0
- pydpeet/process/merge/__init__.py +7 -0
- pydpeet/process/merge/series.py +274 -0
- pydpeet/process/sequence/__init__.py +7 -0
- pydpeet/process/sequence/configs/__init__.py +7 -0
- pydpeet/process/sequence/configs/config.py +17 -0
- pydpeet/process/sequence/step_analyzer.py +440 -0
- pydpeet/process/sequence/utils/__init__.py +7 -0
- pydpeet/process/sequence/utils/annotate/__init__.py +7 -0
- pydpeet/process/sequence/utils/annotate/annotate_primitives.py +419 -0
- pydpeet/process/sequence/utils/configs/CONFIG_Fallback.py +231 -0
- pydpeet/process/sequence/utils/configs/CONFIG_preprocessing.py +243 -0
- pydpeet/process/sequence/utils/configs/__init__.py +7 -0
- pydpeet/process/sequence/utils/console_prints/__init__.py +7 -0
- pydpeet/process/sequence/utils/console_prints/log_time.py +30 -0
- pydpeet/process/sequence/utils/postprocessing/__init__.py +7 -0
- pydpeet/process/sequence/utils/postprocessing/df_primitives_correction.py +166 -0
- pydpeet/process/sequence/utils/postprocessing/filter_df.py +168 -0
- pydpeet/process/sequence/utils/postprocessing/generate_instructions.py +297 -0
- pydpeet/process/sequence/utils/preprocessing/__init__.py +7 -0
- pydpeet/process/sequence/utils/preprocessing/calculate_thresholds.py +27 -0
- pydpeet/process/sequence/utils/processing/__init__.py +7 -0
- pydpeet/process/sequence/utils/processing/analyze_segments.py +368 -0
- pydpeet/process/sequence/utils/processing/attempt_to_merge_neighboring_segments.py +122 -0
- pydpeet/process/sequence/utils/processing/check_CV_results.py +226 -0
- pydpeet/process/sequence/utils/processing/check_power_zero_watt_segments.py +85 -0
- pydpeet/process/sequence/utils/processing/check_zero_length.py +117 -0
- pydpeet/process/sequence/utils/processing/split_in_segments.py +97 -0
- pydpeet/process/sequence/utils/processing/supress_smaller_segments.py +99 -0
- pydpeet/process/sequence/utils/processing/widen_constant_segments.py +185 -0
- pydpeet/process/sequence/utils/visualize/__init__.py +7 -0
- pydpeet/process/sequence/utils/visualize/visualize_data.py +288 -0
- pydpeet/res/__init__.py +7 -0
- pydpeet/settings.py +0 -0
- pydpeet/utils/__init__.py +7 -0
- pydpeet/utils/logging_style.py +28 -0
- pydpeet/version.py +0 -0
- pydpeet-0.2.0.dist-info/METADATA +85 -0
- pydpeet-0.2.0.dist-info/RECORD +122 -0
- pydpeet-0.2.0.dist-info/WHEEL +5 -0
- pydpeet-0.2.0.dist-info/licenses/AUTHORS.md +0 -0
- pydpeet-0.2.0.dist-info/licenses/LICENCE.md +27 -0
- pydpeet-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
from pydpeet.process.sequence.configs import config
|
|
2
|
+
from pydpeet.process.sequence.utils.preprocessing.calculate_thresholds import calculate_minimum_definitive_differences
|
|
3
|
+
|
|
4
|
+
THRESHOLD_DICT_Custom = [ # ARBIN_OLD
|
|
5
|
+
0.0005, # ACCURACY_VOLTAGE_SIGNAL
|
|
6
|
+
0.01, # ACCURACY_CURRENT_SIGNAL
|
|
7
|
+
0.0005, # ACCURACY_VOLTAGE_MEASUREMENT
|
|
8
|
+
0.01, # ACCURACY_CURRENT_MEASUREMENT
|
|
9
|
+
5, # FS_VOLTAGE
|
|
10
|
+
3, # FS_CURRENT
|
|
11
|
+
]
|
|
12
|
+
# use THRESHOLD_DICT = THRESHOLD_DICT_Custom if you don't want to use a predefined dictionary
|
|
13
|
+
THRESHOLD_DICT = config.NEWARE_CT_4008Q_5V12A_S1
|
|
14
|
+
MIN_DEFINITIVE_VOLTAGE_DIFFERENCE, MIN_DEFINITIVE_CURRENT_DIFFERENCE = calculate_minimum_definitive_differences(
|
|
15
|
+
*THRESHOLD_DICT
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
####### depending on the Noise needs to be adjusted even for measurements of the same device #######
|
|
19
|
+
SEGMENTS_TO_DETECT_CONFIG = [
|
|
20
|
+
# devide threshold by 2 because it's looking above and below the target line
|
|
21
|
+
("Voltage[V]", MIN_DEFINITIVE_VOLTAGE_DIFFERENCE / 2),
|
|
22
|
+
("Current[A]", MIN_DEFINITIVE_CURRENT_DIFFERENCE / 2),
|
|
23
|
+
("Power[W]", (MIN_DEFINITIVE_VOLTAGE_DIFFERENCE + MIN_DEFINITIVE_CURRENT_DIFFERENCE) / 2),
|
|
24
|
+
]
|
|
25
|
+
####### depending on the Noise needs to be adjusted even for measurements of the same device #######
|
|
26
|
+
# ORDER IS IMPORTANT!
|
|
27
|
+
ADJUST_SEGMENTS_CONFIG = [
|
|
28
|
+
("Voltage[V]", MIN_DEFINITIVE_VOLTAGE_DIFFERENCE),
|
|
29
|
+
("Current[A]", MIN_DEFINITIVE_CURRENT_DIFFERENCE),
|
|
30
|
+
("Power[W]", (MIN_DEFINITIVE_VOLTAGE_DIFFERENCE + MIN_DEFINITIVE_CURRENT_DIFFERENCE)),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
THRESHOLD_CV_SEGMENTS_0A_END = MIN_DEFINITIVE_CURRENT_DIFFERENCE
|
|
34
|
+
|
|
35
|
+
####### HAS TO USE SAME KEY AS DATA_COLUMNS! only change the values of thresholds! ########
|
|
36
|
+
THRESHOLDS_PRIMITIVE_ANNOTATION = {
|
|
37
|
+
"V": MIN_DEFINITIVE_VOLTAGE_DIFFERENCE,
|
|
38
|
+
"I": MIN_DEFINITIVE_CURRENT_DIFFERENCE,
|
|
39
|
+
"P": MIN_DEFINITIVE_VOLTAGE_DIFFERENCE + MIN_DEFINITIVE_CURRENT_DIFFERENCE,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
########################################################################################################################
|
|
43
|
+
SHOW_RUNTIME = True
|
|
44
|
+
SHOW_RUNTIME_ANNOTATION = True
|
|
45
|
+
SHOW_RUNTIME_ANALYZE = True
|
|
46
|
+
SHOW_RUNTIME_VISUALIZATION = True
|
|
47
|
+
THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK = 2
|
|
48
|
+
THRESHOLD_CONSOLE_PRINTS_CV_CHECK = 2
|
|
49
|
+
THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH = 2
|
|
50
|
+
THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK = 10
|
|
51
|
+
########################################################################################################################
|
|
52
|
+
# Visualize Data
|
|
53
|
+
VISUALIZE_PHASES_CONFIG = [
|
|
54
|
+
("V", "blue"),
|
|
55
|
+
("I", "red"),
|
|
56
|
+
("P", "green"),
|
|
57
|
+
]
|
|
58
|
+
LINE_VISUALIZATION_CONFIG = [
|
|
59
|
+
("Voltage[V]", "blue", (2.4, 4.3)),
|
|
60
|
+
("Current[A]", "red", (-10, 10)),
|
|
61
|
+
("Power[W]", "green", (-40, 20)),
|
|
62
|
+
]
|
|
63
|
+
START = 0
|
|
64
|
+
END = 1e100
|
|
65
|
+
USE_LINES_FOR_SEGMENTS = True
|
|
66
|
+
SHOW_COLUMN_NAMES = True
|
|
67
|
+
SHOW_TIME = True
|
|
68
|
+
SHOW_ID = True
|
|
69
|
+
SEGMENT_ALPHA = 0.3
|
|
70
|
+
WIDTH_HEIGHT_RATIO = [1, 0.3]
|
|
71
|
+
########################################################################################################################
|
|
72
|
+
# Generate Instructions
|
|
73
|
+
END_CONDITION_MAP_GENERATE_INSTRUCTIONS = {
|
|
74
|
+
"CC": "voltage",
|
|
75
|
+
"CV": "current",
|
|
76
|
+
"CP": "voltage",
|
|
77
|
+
"Pause": "time",
|
|
78
|
+
}
|
|
79
|
+
########################################################################################################################
|
|
80
|
+
SEQUENCES_CONFIG: dict[str, dict] = {
|
|
81
|
+
# Complex Sequences
|
|
82
|
+
# Loop rules: "loop": True, "exact_loops": 2, "min_loops": 2, "max_loops": 2, "minimum_IDs": 6
|
|
83
|
+
"Discharge_iOCV": {"loop": True, "minimum_IDs": 4, "sequence": ["CC_Discharge", "Pause"]},
|
|
84
|
+
"Charge_iOCV": {"loop": True, "min_loops": 2, "sequence": ["Pause", "CC_Charge"]},
|
|
85
|
+
"CCCV_Charge": {"loop": False, "sequence": ["CC_Charge", "CV_Charge"]},
|
|
86
|
+
}
|
|
87
|
+
SEGMENTS_CONFIG_SIMPLE: dict[str, dict] = {
|
|
88
|
+
# Primitive segments
|
|
89
|
+
# I, V, P,
|
|
90
|
+
# Charging, Discharging
|
|
91
|
+
"I": {
|
|
92
|
+
"rules": {
|
|
93
|
+
"variable": "I",
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"V": {
|
|
97
|
+
"rules": {
|
|
98
|
+
"variable": "V",
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"P": {
|
|
102
|
+
"rules": {
|
|
103
|
+
"variable": "P",
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"Charging": {
|
|
107
|
+
"rules": {
|
|
108
|
+
"direction": "Charge",
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"Discharging": {
|
|
112
|
+
"rules": {
|
|
113
|
+
"direction": "Discharge",
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
SEGMENTS_CONFIG_STANDARD: dict[str, dict] = {
|
|
118
|
+
# Primitive segments
|
|
119
|
+
# Pause
|
|
120
|
+
# CC_Charge, CV_Charge, CP_Charge
|
|
121
|
+
# CC_Discharge, CV_Discharge, CP_Discharge
|
|
122
|
+
# Ramp_Current_Charge, Ramp_Voltage_Charge, Ramp_Power_Charge
|
|
123
|
+
# Ramp_Current_Discharge, Ramp_Voltage_Discharge, Ramp_Power_Discharge
|
|
124
|
+
"Pause": {
|
|
125
|
+
"rules": {
|
|
126
|
+
"type": "Rest",
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"CC_Charge": {
|
|
130
|
+
"rules": {
|
|
131
|
+
"variable": "I",
|
|
132
|
+
"type": "Constant",
|
|
133
|
+
"direction": "Charge",
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"CV_Charge": {
|
|
137
|
+
"rules": {
|
|
138
|
+
"variable": "V",
|
|
139
|
+
"type": "Constant",
|
|
140
|
+
"direction": "Charge",
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"CP_Charge": {
|
|
144
|
+
"rules": {
|
|
145
|
+
"variable": "P",
|
|
146
|
+
"type": "Constant",
|
|
147
|
+
"direction": "Charge",
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"CC_Discharge": {
|
|
151
|
+
"rules": {
|
|
152
|
+
"variable": "I",
|
|
153
|
+
"type": "Constant",
|
|
154
|
+
"direction": "Discharge",
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"CV_Discharge": {
|
|
158
|
+
"rules": {
|
|
159
|
+
"variable": "V",
|
|
160
|
+
"type": "Constant",
|
|
161
|
+
"direction": "Discharge",
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"CP_Discharge": {
|
|
165
|
+
"rules": {
|
|
166
|
+
"variable": "P",
|
|
167
|
+
"type": "Constant",
|
|
168
|
+
"direction": "Discharge",
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"CRamp_Charge": {
|
|
172
|
+
"rules": {
|
|
173
|
+
"variable": "I",
|
|
174
|
+
"type": "Ramp",
|
|
175
|
+
"direction": "Up",
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"VRamp_Charge": {
|
|
179
|
+
"rules": {
|
|
180
|
+
"variable": "V",
|
|
181
|
+
"type": "Ramp",
|
|
182
|
+
"direction": "Up",
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
"PRamp_Charge": {
|
|
186
|
+
"rules": {
|
|
187
|
+
"variable": "P",
|
|
188
|
+
"type": "Ramp",
|
|
189
|
+
"direction": "Up",
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
"CRamp_Discharge": {
|
|
193
|
+
"rules": {
|
|
194
|
+
"variable": "I",
|
|
195
|
+
"type": "Ramp",
|
|
196
|
+
"direction": "Down",
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
"VRamp_Discharge": {
|
|
200
|
+
"rules": {
|
|
201
|
+
"variable": "V",
|
|
202
|
+
"type": "Ramp",
|
|
203
|
+
"direction": "Down",
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"PRamp_Discharge": {
|
|
207
|
+
"rules": {
|
|
208
|
+
"variable": "P",
|
|
209
|
+
"type": "Ramp",
|
|
210
|
+
"direction": "Down",
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
SEGMENT_SEQUENCE_CONFIG = {
|
|
217
|
+
**SEQUENCES_CONFIG,
|
|
218
|
+
**SEGMENTS_CONFIG_STANDARD,
|
|
219
|
+
**SEGMENTS_CONFIG_SIMPLE,
|
|
220
|
+
}
|
|
221
|
+
########################################################################################################################
|
|
222
|
+
#### These shouldn't be changed when using ppb Dataframes ####
|
|
223
|
+
DATA_COLUMNS = {
|
|
224
|
+
"V": "Voltage[V]",
|
|
225
|
+
"I": "Current[A]",
|
|
226
|
+
"P": "Power[W]",
|
|
227
|
+
}
|
|
228
|
+
#### These shouldn't be changed when using ppb Dataframes ####
|
|
229
|
+
|
|
230
|
+
STEP_ANALYZER_PRIMITIVES_CONFIG_PRECOMPILE = {
|
|
231
|
+
"SEGMENTS_TO_DETECT_CONFIG": SEGMENTS_TO_DETECT_CONFIG,
|
|
232
|
+
"ADJUST_SEGMENTS_CONFIG": ADJUST_SEGMENTS_CONFIG,
|
|
233
|
+
"THRESHOLDS_PRIMITIVE_ANNOTATION": THRESHOLDS_PRIMITIVE_ANNOTATION,
|
|
234
|
+
"SHOW_RUNTIME": SHOW_RUNTIME,
|
|
235
|
+
"SHOW_RUNTIME_ANNOTATION": SHOW_RUNTIME_ANNOTATION,
|
|
236
|
+
"DATA_COLUMNS": DATA_COLUMNS,
|
|
237
|
+
"MIN_DEFINITIVE_CURRENT_DIFFERENCE": MIN_DEFINITIVE_CURRENT_DIFFERENCE,
|
|
238
|
+
"THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK": THRESHOLD_CONSOLE_PRINTS_ZERO_LENGTH_CHECK,
|
|
239
|
+
"THRESHOLD_CONSOLE_PRINTS_CV_CHECK": THRESHOLD_CONSOLE_PRINTS_CV_CHECK,
|
|
240
|
+
"THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH": THRESHOLD_CONSOLE_PRINTS_FINETUNING_WIDTH,
|
|
241
|
+
"THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK": THRESHOLD_CONSOLE_PRINTS_POWER_ZERO_WATT_CHECK,
|
|
242
|
+
"THRESHOLD_CV_SEGMENTS_0A_END": THRESHOLD_CV_SEGMENTS_0A_END,
|
|
243
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from time import perf_counter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@contextmanager
|
|
7
|
+
def log_time(
|
|
8
|
+
description: str = "",
|
|
9
|
+
SHOW_RUNTIME: bool = True,
|
|
10
|
+
):
|
|
11
|
+
"""
|
|
12
|
+
Context manager to log the time taken by a block of code.
|
|
13
|
+
|
|
14
|
+
The log message will show the time taken in seconds with 4 decimal places,
|
|
15
|
+
and the description passed as argument.
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
description: str
|
|
19
|
+
The description of the block of code.
|
|
20
|
+
SHOW_RUNTIME: bool
|
|
21
|
+
Whether to print the time taken.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
None
|
|
25
|
+
"""
|
|
26
|
+
start = perf_counter()
|
|
27
|
+
yield
|
|
28
|
+
end = perf_counter()
|
|
29
|
+
if SHOW_RUNTIME:
|
|
30
|
+
logging.info(f" {end - start:.4f}s {description}")
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from pydpeet.process.sequence.utils.annotate.annotate_primitives import _merged_annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def df_primitives_correction(
|
|
7
|
+
df_primitives: pd.DataFrame,
|
|
8
|
+
correction_config: dict,
|
|
9
|
+
data_columns: dict[str, str],
|
|
10
|
+
thresholds: dict[str, float],
|
|
11
|
+
reindex: bool = True,
|
|
12
|
+
reannotate: bool = True,
|
|
13
|
+
) -> pd.DataFrame:
|
|
14
|
+
"""
|
|
15
|
+
Corrects the primitives in the dataframe based on the given configuration.
|
|
16
|
+
|
|
17
|
+
Parameters:
|
|
18
|
+
df_primitives (pd.DataFrame): Input dataframe containing the primitives.
|
|
19
|
+
correction_config (dict): Configuration for the correction, containing the following keys:
|
|
20
|
+
- replace_ID (dict): Mapping of old IDs to new IDs.
|
|
21
|
+
- replace_time (dict): Mapping of time ranges and the new labels for variable.
|
|
22
|
+
- replace_time_and_merge (dict): Mapping of time ranges and the new labels for variable + merges all into one segment.
|
|
23
|
+
- merge_left (list): List of IDs to merge with the previous segment.
|
|
24
|
+
- merge_right (list): List of IDs to merge with the next segment.
|
|
25
|
+
- merge_range (list): List of tuples of start and end IDs to merge. Keeping left Variable
|
|
26
|
+
Example: {'replace_time': {(0.0, 10.0): 'I', (10.0, 20.0): 'P'}, ...}
|
|
27
|
+
data_columns (dict): Mapping of column names to their respective short names.
|
|
28
|
+
Example: data_columns = {"I": "Current[A]", "P": "Power[W]", "V": "Voltage[V]"}
|
|
29
|
+
thresholds (dict): Threshold values for each type of annotation.
|
|
30
|
+
Example: {"V": 0.1, "I": 0.1, "P": 0.1}
|
|
31
|
+
reindex (bool): Whether to reindex the IDs to be consecutive, by default True.
|
|
32
|
+
reannotate (bool): Whether to reannotate the primitives after the correction, by default True.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
pd.DataFrame: The corrected dataframe with the added columns for annotated primitives.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
df = df_primitives.copy()
|
|
39
|
+
max_id = df["ID"].max() + 1 # for new IDs
|
|
40
|
+
|
|
41
|
+
# --- REPLACE by ID ---
|
|
42
|
+
if "replace_ID" in correction_config:
|
|
43
|
+
for id_, new_label in correction_config["replace_ID"].items():
|
|
44
|
+
df.loc[df["ID"] == id_, "Variable"] = new_label
|
|
45
|
+
|
|
46
|
+
# --- REPLACE by TIME with splitting ---
|
|
47
|
+
if "replace_time" in correction_config:
|
|
48
|
+
for (start, end), new_label in correction_config["replace_time"].items():
|
|
49
|
+
overlapping_idx = df[(df["Test_Time[s]"] >= start) & (df["Test_Time[s]"] <= end)].index
|
|
50
|
+
if overlapping_idx.empty:
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
overlapping_ids = df.loc[overlapping_idx, "ID"].unique()
|
|
54
|
+
for seg_id in overlapping_ids:
|
|
55
|
+
segment_rows = df[df["ID"] == seg_id]
|
|
56
|
+
seg_start_time = segment_rows["Test_Time[s]"].min()
|
|
57
|
+
seg_end_time = segment_rows["Test_Time[s]"].max()
|
|
58
|
+
|
|
59
|
+
if start <= seg_start_time and end >= seg_end_time:
|
|
60
|
+
mask = df["ID"] == seg_id
|
|
61
|
+
df.loc[mask, "Variable"] = new_label
|
|
62
|
+
else:
|
|
63
|
+
if seg_start_time < start:
|
|
64
|
+
mask_before = (df["ID"] == seg_id) & (df["Test_Time[s]"] < start)
|
|
65
|
+
df.loc[mask_before, "ID"] = max_id
|
|
66
|
+
max_id += 1
|
|
67
|
+
|
|
68
|
+
mask_overlap = (df["ID"] == seg_id) & (df["Test_Time[s]"] >= start) & (df["Test_Time[s]"] <= end)
|
|
69
|
+
df.loc[mask_overlap, "Variable"] = new_label
|
|
70
|
+
df.loc[mask_overlap, "ID"] = max_id
|
|
71
|
+
max_id += 1
|
|
72
|
+
|
|
73
|
+
if seg_end_time > end:
|
|
74
|
+
mask_after = (df["ID"] == seg_id) & (df["Test_Time[s]"] > end)
|
|
75
|
+
df.loc[mask_after, "ID"] = max_id
|
|
76
|
+
max_id += 1
|
|
77
|
+
|
|
78
|
+
# --- REPLACE by TIME and MERGE ---
|
|
79
|
+
if "replace_time_and_merge" in correction_config:
|
|
80
|
+
for (start, end), new_label in correction_config["replace_time_and_merge"].items():
|
|
81
|
+
overlapping_idx = df[(df["Test_Time[s]"] >= start) & (df["Test_Time[s]"] <= end)].index
|
|
82
|
+
if overlapping_idx.empty:
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
overlapping_ids = df.loc[overlapping_idx, "ID"].unique()
|
|
86
|
+
new_id_for_merge = max_id
|
|
87
|
+
max_id += 1
|
|
88
|
+
|
|
89
|
+
for seg_id in overlapping_ids:
|
|
90
|
+
segment_rows = df[df["ID"] == seg_id]
|
|
91
|
+
seg_start_time = segment_rows["Test_Time[s]"].min()
|
|
92
|
+
seg_end_time = segment_rows["Test_Time[s]"].max()
|
|
93
|
+
|
|
94
|
+
if start <= seg_start_time and end >= seg_end_time:
|
|
95
|
+
mask = df["ID"] == seg_id
|
|
96
|
+
df.loc[mask, "Variable"] = new_label
|
|
97
|
+
df.loc[mask, "ID"] = new_id_for_merge
|
|
98
|
+
else:
|
|
99
|
+
if seg_start_time < start:
|
|
100
|
+
mask_before = (df["ID"] == seg_id) & (df["Test_Time[s]"] < start)
|
|
101
|
+
df.loc[mask_before, "ID"] = max_id
|
|
102
|
+
max_id += 1
|
|
103
|
+
|
|
104
|
+
mask_overlap = (df["ID"] == seg_id) & (df["Test_Time[s]"] >= start) & (df["Test_Time[s]"] <= end)
|
|
105
|
+
df.loc[mask_overlap, "Variable"] = new_label
|
|
106
|
+
df.loc[mask_overlap, "ID"] = new_id_for_merge
|
|
107
|
+
|
|
108
|
+
if seg_end_time > end:
|
|
109
|
+
mask_after = (df["ID"] == seg_id) & (df["Test_Time[s]"] > end)
|
|
110
|
+
df.loc[mask_after, "ID"] = max_id
|
|
111
|
+
max_id += 1
|
|
112
|
+
|
|
113
|
+
# --- MERGE LEFT ---
|
|
114
|
+
if "merge_left" in correction_config:
|
|
115
|
+
for id_ in correction_config["merge_left"]:
|
|
116
|
+
segment_rows = df[df["ID"] == id_]
|
|
117
|
+
if segment_rows.empty:
|
|
118
|
+
continue
|
|
119
|
+
first_pos = df.index.get_loc(segment_rows.index[0])
|
|
120
|
+
if first_pos == 0:
|
|
121
|
+
continue
|
|
122
|
+
left_id = df.iloc[first_pos - 1]["ID"]
|
|
123
|
+
left_variable = df.iloc[first_pos - 1]["Variable"]
|
|
124
|
+
df.loc[segment_rows.index, "ID"] = left_id
|
|
125
|
+
df.loc[segment_rows.index, "Variable"] = left_variable
|
|
126
|
+
|
|
127
|
+
# --- MERGE RIGHT ---
|
|
128
|
+
if "merge_right" in correction_config:
|
|
129
|
+
for id_ in correction_config["merge_right"]:
|
|
130
|
+
segment_rows = df[df["ID"] == id_]
|
|
131
|
+
if segment_rows.empty:
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
first_pos = df.index.get_loc(segment_rows.index[0])
|
|
135
|
+
last_pos = df.index.get_loc(segment_rows.index[-1])
|
|
136
|
+
|
|
137
|
+
right_pos = last_pos + 1
|
|
138
|
+
if right_pos >= len(df):
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
right_id = df.iloc[right_pos]["ID"]
|
|
142
|
+
right_variable = df.iloc[right_pos]["Variable"]
|
|
143
|
+
df.loc[segment_rows.index, "ID"] = right_id
|
|
144
|
+
df.loc[segment_rows.index, "Variable"] = right_variable
|
|
145
|
+
|
|
146
|
+
# --- MERGE RANGES ---
|
|
147
|
+
if "merge_range" in correction_config:
|
|
148
|
+
for start_id, end_id in correction_config["merge_range"]:
|
|
149
|
+
mask = (df["ID"] >= start_id) & (df["ID"] <= end_id)
|
|
150
|
+
if not mask.any():
|
|
151
|
+
continue
|
|
152
|
+
target_id = start_id
|
|
153
|
+
target_variable = df.loc[df["ID"] == target_id, "Variable"].iloc[0]
|
|
154
|
+
df.loc[mask, "ID"] = target_id
|
|
155
|
+
df.loc[mask, "Variable"] = target_variable
|
|
156
|
+
|
|
157
|
+
# --- REINDEX IDs to be consecutive ---
|
|
158
|
+
if reindex:
|
|
159
|
+
unique_ids = df["ID"].drop_duplicates().reset_index(drop=True)
|
|
160
|
+
id_mapping = {old_id: new_id for new_id, old_id in enumerate(unique_ids, start=1)}
|
|
161
|
+
df["ID"] = df["ID"].map(id_mapping)
|
|
162
|
+
|
|
163
|
+
if reannotate:
|
|
164
|
+
df = _merged_annotations(df, data_columns, thresholds)
|
|
165
|
+
|
|
166
|
+
return df
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import operator
|
|
3
|
+
from functools import reduce
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from pandas import DataFrame
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# TODO: Docstring
|
|
10
|
+
def filter_df(
|
|
11
|
+
df_segments_and_sequences: pd.DataFrame,
|
|
12
|
+
df_primitives: pd.DataFrame,
|
|
13
|
+
rules: list[str],
|
|
14
|
+
standard_columns: list[str],
|
|
15
|
+
combine_op: str = "xor",
|
|
16
|
+
) -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
17
|
+
# Map string to actual function
|
|
18
|
+
comb_funcs = {"and": operator.and_, "or": operator.or_, "xor": operator.xor, "not": operator.not_}
|
|
19
|
+
if combine_op not in comb_funcs:
|
|
20
|
+
raise ValueError(f"combine_op must be one of {list(comb_funcs)}")
|
|
21
|
+
|
|
22
|
+
if not rules:
|
|
23
|
+
# No filtering, use all IDs
|
|
24
|
+
df_filtered_IDs = df_segments_and_sequences["ID"].values
|
|
25
|
+
else:
|
|
26
|
+
# build list of boolean Series for each rule
|
|
27
|
+
masks = []
|
|
28
|
+
for col in rules:
|
|
29
|
+
if col not in df_segments_and_sequences:
|
|
30
|
+
raise KeyError(f"column {col!r} not in DataFrame")
|
|
31
|
+
masks.append(operator.ne(df_segments_and_sequences[col], 0))
|
|
32
|
+
|
|
33
|
+
# combine masks with selected operator
|
|
34
|
+
combined_mask = reduce(comb_funcs[combine_op], masks)
|
|
35
|
+
df_filtered_IDs = df_segments_and_sequences[combined_mask]["ID"].values
|
|
36
|
+
|
|
37
|
+
# Create standard and non-standard dataframes
|
|
38
|
+
df_standard = df_primitives[standard_columns + ["ID", "Power[W]"]]
|
|
39
|
+
df_non_standard = df_primitives[
|
|
40
|
+
[col for col in df_primitives.columns if col not in standard_columns + ["Power[W]"]]
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
# Nullify non-standard rows not in filtered IDs
|
|
44
|
+
mask = ~df_non_standard["ID"].isin(df_filtered_IDs)
|
|
45
|
+
cols_to_null = [col for col in df_non_standard.columns if col != "ID"]
|
|
46
|
+
df_non_standard.loc[mask, cols_to_null] = None
|
|
47
|
+
df_non_standard.loc[mask, "Variable"] = "Filtered"
|
|
48
|
+
|
|
49
|
+
# Combine standard and non-standard
|
|
50
|
+
df_filtered = pd.concat([df_standard, df_non_standard.drop(columns="ID")], axis=1)
|
|
51
|
+
|
|
52
|
+
return df_filtered, df_filtered_IDs
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# TODO: Docstring
|
|
56
|
+
def return_or_print_blocks(
|
|
57
|
+
df_filtered: pd.DataFrame,
|
|
58
|
+
filtered_IDs: pd.Series | list | set | tuple,
|
|
59
|
+
print_blocks: bool = True,
|
|
60
|
+
) -> None | list[dict]:
|
|
61
|
+
import numpy as np
|
|
62
|
+
|
|
63
|
+
# Ensure IDs are in a fast lookup structure
|
|
64
|
+
filtered_IDs_set = set(filtered_IDs)
|
|
65
|
+
|
|
66
|
+
# Convert necessary columns to arrays
|
|
67
|
+
ids = df_filtered["ID"].values
|
|
68
|
+
testtimes = df_filtered["Test_Time[s]"].values
|
|
69
|
+
is_filtered = df_filtered["Variable"].values == "Filtered"
|
|
70
|
+
|
|
71
|
+
# Boolean mask for unfiltered rows
|
|
72
|
+
unfiltered_mask = np.isin(ids, list(filtered_IDs_set)) & ~is_filtered
|
|
73
|
+
|
|
74
|
+
# Find block boundaries (diff between 0/1 in mask)
|
|
75
|
+
block_edges = np.diff(unfiltered_mask.astype(int))
|
|
76
|
+
starts = np.where(block_edges == 1)[0] + 1
|
|
77
|
+
ends = np.where(block_edges == -1)[0]
|
|
78
|
+
|
|
79
|
+
# Edge case: starts/ends at beginning or end
|
|
80
|
+
if unfiltered_mask[0]:
|
|
81
|
+
starts = np.insert(starts, 0, 0)
|
|
82
|
+
if unfiltered_mask[-1]:
|
|
83
|
+
ends = np.append(ends, len(unfiltered_mask) - 1)
|
|
84
|
+
|
|
85
|
+
blocks = []
|
|
86
|
+
for i, (start_idx, end_idx) in enumerate(zip(starts, ends, strict=False), start=-1):
|
|
87
|
+
start_id = ids[start_idx]
|
|
88
|
+
end_id = ids[end_idx]
|
|
89
|
+
start_time = testtimes[start_idx]
|
|
90
|
+
end_time = testtimes[end_idx]
|
|
91
|
+
if print_blocks:
|
|
92
|
+
logging.info("-" * 40)
|
|
93
|
+
logging.info(f"Block {i + 1}:")
|
|
94
|
+
logging.info(f" Start ID: {start_id}, Test_Time[s]: {start_time}")
|
|
95
|
+
logging.info(f" End ID: {end_id}, Test_Time[s]: {end_time}")
|
|
96
|
+
logging.info("-" * 40)
|
|
97
|
+
|
|
98
|
+
blocks.append(
|
|
99
|
+
{
|
|
100
|
+
"start_id": start_id,
|
|
101
|
+
"end_id": end_id,
|
|
102
|
+
"start_time": start_time,
|
|
103
|
+
"end_time": end_time,
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return blocks
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def split_df_by_blocks(
|
|
111
|
+
df_filtered: pd.DataFrame,
|
|
112
|
+
blocks: list[dict],
|
|
113
|
+
) -> list[pd.DataFrame]:
|
|
114
|
+
"""
|
|
115
|
+
Split df_filtered into multiple DataFrames per block.
|
|
116
|
+
Each block includes all rows from start_id to end_id (inclusive), even if IDs repeat.
|
|
117
|
+
"""
|
|
118
|
+
dfs_per_block = []
|
|
119
|
+
ids = df_filtered["ID"].values
|
|
120
|
+
|
|
121
|
+
for block in blocks:
|
|
122
|
+
start_id = block["start_id"]
|
|
123
|
+
end_id = block["end_id"]
|
|
124
|
+
|
|
125
|
+
# Find ALL positions of start_id and end_id
|
|
126
|
+
start_positions = (ids == start_id).nonzero()[0]
|
|
127
|
+
end_positions = (ids == end_id).nonzero()[0]
|
|
128
|
+
|
|
129
|
+
if len(start_positions) == 0 or len(end_positions) == 0:
|
|
130
|
+
continue # skip if not found
|
|
131
|
+
|
|
132
|
+
start_idx = start_positions[0]
|
|
133
|
+
end_idx = end_positions[-1]
|
|
134
|
+
|
|
135
|
+
df_block = df_filtered.iloc[start_idx : end_idx + 1].copy()
|
|
136
|
+
dfs_per_block.append(df_block)
|
|
137
|
+
|
|
138
|
+
return dfs_per_block
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def filter_and_split_df_by_blocks(
|
|
142
|
+
df_segments_and_sequences: pd.DataFrame,
|
|
143
|
+
df_primitives: pd.DataFrame,
|
|
144
|
+
rules: list[str],
|
|
145
|
+
combine_op: str = "or",
|
|
146
|
+
print_blocks: bool = False,
|
|
147
|
+
also_return_filtered_df: bool = True,
|
|
148
|
+
) -> tuple[list[DataFrame], DataFrame] | list[DataFrame]:
|
|
149
|
+
standard_columns = ["Test_Time[s]", "Voltage[V]", "Current[A]", "Power[W]"]
|
|
150
|
+
logging.warning("Using default standard columns:")
|
|
151
|
+
logging.warning(standard_columns)
|
|
152
|
+
|
|
153
|
+
df_filtered, df_filtered_IDs = filter_df(
|
|
154
|
+
df_segments_and_sequences=df_segments_and_sequences,
|
|
155
|
+
df_primitives=df_primitives,
|
|
156
|
+
rules=rules,
|
|
157
|
+
combine_op=combine_op,
|
|
158
|
+
standard_columns=standard_columns,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
blocks = return_or_print_blocks(df_filtered=df_filtered, filtered_IDs=df_filtered_IDs, print_blocks=print_blocks)
|
|
162
|
+
|
|
163
|
+
dfs_per_block = split_df_by_blocks(df_filtered=df_filtered, blocks=blocks)
|
|
164
|
+
|
|
165
|
+
if also_return_filtered_df:
|
|
166
|
+
return dfs_per_block, df_filtered
|
|
167
|
+
|
|
168
|
+
return dfs_per_block
|