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
pydpeet/io/convert.py
ADDED
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from pandas import Index
|
|
10
|
+
|
|
11
|
+
from pydpeet.io.configs.config import (
|
|
12
|
+
FORMATTER_CONFIGS,
|
|
13
|
+
MAPPER_CONFIGS,
|
|
14
|
+
READER_CONFIGS,
|
|
15
|
+
STANDARD_COLUMNS,
|
|
16
|
+
Config,
|
|
17
|
+
DataOutputFiletype,
|
|
18
|
+
)
|
|
19
|
+
from pydpeet.io.device.neware_8_0_0_516.reader import find_main_files
|
|
20
|
+
from pydpeet.io.map import mapping
|
|
21
|
+
from pydpeet.io.utils.ext_path import ExtPath
|
|
22
|
+
from pydpeet.io.utils.load_custom_module import load_custom_module
|
|
23
|
+
from pydpeet.io.utils.timing import measure_time
|
|
24
|
+
from pydpeet.io.write import write
|
|
25
|
+
|
|
26
|
+
ConfigLike = Config | str
|
|
27
|
+
PathLike = str | Path
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def convert(
|
|
31
|
+
config: ConfigLike,
|
|
32
|
+
input_path: object,
|
|
33
|
+
output_path: str = None,
|
|
34
|
+
keep_all_additional_data: bool = False,
|
|
35
|
+
custom_folder_path: str = None,
|
|
36
|
+
) -> pd.DataFrame | list[pd.DataFrame] | None:
|
|
37
|
+
if isinstance(input_path, str):
|
|
38
|
+
if os.path.isfile(input_path):
|
|
39
|
+
return convert_file(config, input_path, output_path, keep_all_additional_data, custom_folder_path)
|
|
40
|
+
elif os.path.isdir(input_path):
|
|
41
|
+
return convert_files_in_directory(
|
|
42
|
+
config, input_path, output_path, keep_all_additional_data, custom_folder_path
|
|
43
|
+
)
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError("Input path is invalid!")
|
|
46
|
+
elif isinstance(input_path, list):
|
|
47
|
+
dfs = []
|
|
48
|
+
for input_item in input_path:
|
|
49
|
+
if isinstance(input_item, str):
|
|
50
|
+
if os.path.isfile(input_item):
|
|
51
|
+
dfs.append(
|
|
52
|
+
convert_file(config, input_item, output_path, keep_all_additional_data, custom_folder_path)
|
|
53
|
+
)
|
|
54
|
+
elif os.path.isdir(input_item):
|
|
55
|
+
dfs.append(
|
|
56
|
+
convert_files_in_directory(
|
|
57
|
+
config, input_item, output_path, keep_all_additional_data, custom_folder_path
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError("Input path item is invalid!")
|
|
62
|
+
else:
|
|
63
|
+
raise ValueError("Input path item is of invalid type!")
|
|
64
|
+
return dfs
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError("Input path is of invalid type!")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# TODO: Add output path functionality
|
|
70
|
+
@measure_time
|
|
71
|
+
def convert_file(
|
|
72
|
+
config: ConfigLike,
|
|
73
|
+
input_path: str,
|
|
74
|
+
output_path: str = None,
|
|
75
|
+
keep_all_additional_data: bool = False,
|
|
76
|
+
custom_folder_path: str = None,
|
|
77
|
+
) -> pd.DataFrame:
|
|
78
|
+
"""
|
|
79
|
+
Standardize a measurement file according to the given configuration and returns the standardized DataFrame.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
config : Config
|
|
84
|
+
The configuration to use for standardizing the file.
|
|
85
|
+
input_path : str
|
|
86
|
+
The path to the file to standardize.
|
|
87
|
+
keep_all_additional_data : bool, optional
|
|
88
|
+
Whether to keep all additional data in the output DataFrame. If False, any
|
|
89
|
+
columns not specified in the configuration will be dropped. Defaults to
|
|
90
|
+
False.
|
|
91
|
+
custom_folder_path : str, optional
|
|
92
|
+
The path to the directory containing the custom reader, mapper, and
|
|
93
|
+
formatter reference for the given configuration.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
DataFrame
|
|
98
|
+
The standardized DataFrame.
|
|
99
|
+
"""
|
|
100
|
+
if isinstance(config, str):
|
|
101
|
+
config = Config.from_string(config)
|
|
102
|
+
if Config.not_exists(config):
|
|
103
|
+
raise ValueError("Config must be provided!")
|
|
104
|
+
if ExtPath.is_not_valid(input_path):
|
|
105
|
+
raise ValueError("Input_path must be provided!")
|
|
106
|
+
if custom_folder_path is not None and ExtPath.is_not_valid(custom_folder_path):
|
|
107
|
+
raise ValueError("Custom_folder_path must be valid if provided!")
|
|
108
|
+
|
|
109
|
+
df, meta_data = _convert_file_to_pandas_data_frame(config, input_path, custom_folder_path)
|
|
110
|
+
df = _column_mapping(df, config, custom_folder_path)
|
|
111
|
+
if not keep_all_additional_data:
|
|
112
|
+
df = _drop_additional_data(df)
|
|
113
|
+
df = _add_metadata_to_dataframe(df, meta_data)
|
|
114
|
+
df = _reorder_columns(df)
|
|
115
|
+
df = _get_data_into_format(df, config, custom_folder_path)
|
|
116
|
+
|
|
117
|
+
return df
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# TODO: Implement better way of handling case where output_path is None?
|
|
121
|
+
@measure_time
|
|
122
|
+
def convert_files_in_directory(
|
|
123
|
+
config: Config,
|
|
124
|
+
input_path: str,
|
|
125
|
+
output_path: str = None,
|
|
126
|
+
keep_all_additional_data: bool = False,
|
|
127
|
+
custom_folder_path: str = None,
|
|
128
|
+
) -> list[pd.DataFrame] | None:
|
|
129
|
+
"""
|
|
130
|
+
Standardize a directory of files according to the given configuration and outputs them to an output_path.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
config : Config
|
|
135
|
+
The configuration to use for standardizing the directory.
|
|
136
|
+
input_path : str
|
|
137
|
+
The path to the directory to standardize.
|
|
138
|
+
output_path : str
|
|
139
|
+
The path to the directory where the standardized files will be written.
|
|
140
|
+
keep_all_additional_data : bool, optional
|
|
141
|
+
Whether to keep all additional data in the output files. If False, any
|
|
142
|
+
columns not specified in the configuration will be dropped. Defaults to
|
|
143
|
+
False.
|
|
144
|
+
custom_folder_path : str, optional
|
|
145
|
+
The path to the directory containing the custom reader, mapper, and
|
|
146
|
+
formatter reference for the given configuration.
|
|
147
|
+
"""
|
|
148
|
+
if config is None:
|
|
149
|
+
raise ValueError("Config must be provided!")
|
|
150
|
+
if input_path is None:
|
|
151
|
+
raise ValueError("Input_path must be provided!")
|
|
152
|
+
# if output_path is None:
|
|
153
|
+
# raise ValueError("output_path must be provided")
|
|
154
|
+
if not isinstance(keep_all_additional_data, bool):
|
|
155
|
+
raise ValueError("Keep_all_additional_data must be a boolean if provided!")
|
|
156
|
+
if custom_folder_path is not None and custom_folder_path is not str:
|
|
157
|
+
raise ValueError("Custom_folder_path must be a string if provided!")
|
|
158
|
+
|
|
159
|
+
current_date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
160
|
+
|
|
161
|
+
if type(config) is str:
|
|
162
|
+
config_name = config
|
|
163
|
+
else:
|
|
164
|
+
config_name = config.name
|
|
165
|
+
|
|
166
|
+
if config == Config.Neware_8_0_0_516:
|
|
167
|
+
files = find_main_files(input_path)
|
|
168
|
+
else:
|
|
169
|
+
files = os.listdir(input_path)
|
|
170
|
+
|
|
171
|
+
if output_path is None:
|
|
172
|
+
dfs = []
|
|
173
|
+
for filename in files:
|
|
174
|
+
df = _process_file(
|
|
175
|
+
config,
|
|
176
|
+
config_name,
|
|
177
|
+
current_date,
|
|
178
|
+
custom_folder_path,
|
|
179
|
+
os.path.join(input_path, filename),
|
|
180
|
+
filename,
|
|
181
|
+
keep_all_additional_data,
|
|
182
|
+
)
|
|
183
|
+
dfs.append(df)
|
|
184
|
+
return dfs
|
|
185
|
+
else:
|
|
186
|
+
os.makedirs(output_path, exist_ok=True)
|
|
187
|
+
for filename in files:
|
|
188
|
+
_process_file_and_export(
|
|
189
|
+
config,
|
|
190
|
+
config_name,
|
|
191
|
+
current_date,
|
|
192
|
+
custom_folder_path,
|
|
193
|
+
DataOutputFiletype.parquet,
|
|
194
|
+
os.path.join(input_path, filename),
|
|
195
|
+
filename,
|
|
196
|
+
keep_all_additional_data,
|
|
197
|
+
output_path,
|
|
198
|
+
)
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _process_file(
|
|
203
|
+
config: Config,
|
|
204
|
+
config_name: str,
|
|
205
|
+
current_date: str,
|
|
206
|
+
custom_folder_path: str,
|
|
207
|
+
file_path: str,
|
|
208
|
+
filename: str,
|
|
209
|
+
keep_all_additional_data: bool,
|
|
210
|
+
) -> pd.DataFrame | None:
|
|
211
|
+
"""
|
|
212
|
+
Process a single file and save the standardized DataFrame to the given output_path.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
config : Config
|
|
217
|
+
The configuration to use for standardizing the file.
|
|
218
|
+
config_name : str
|
|
219
|
+
The name of the configuration.
|
|
220
|
+
current_date : str
|
|
221
|
+
The current date in the format %Y-%m-%d_%H-%M-%S.
|
|
222
|
+
custom_folder_path : str
|
|
223
|
+
The path to the directory containing the custom reader, mapper, and
|
|
224
|
+
formatter reference for the given configuration.
|
|
225
|
+
data_output_filetype : DataOutputFiletype
|
|
226
|
+
The file type to use when exporting the data.
|
|
227
|
+
file_path : str
|
|
228
|
+
The path to the file to standardize.
|
|
229
|
+
filename : str
|
|
230
|
+
The name of the file to standardize.
|
|
231
|
+
keep_all_additional_data : bool
|
|
232
|
+
Whether to keep all additional data in the output DataFrame.
|
|
233
|
+
output_path : str
|
|
234
|
+
The path to the directory where the standardized file will be written.
|
|
235
|
+
"""
|
|
236
|
+
logging.info(f"Processing file: {filename}")
|
|
237
|
+
try:
|
|
238
|
+
df = convert_file(config, file_path, keep_all_additional_data, custom_folder_path)
|
|
239
|
+
output_filename = f"{os.path.splitext(filename)[0]}_{config_name}_{current_date}"
|
|
240
|
+
logging.info(f"Successfully processed: {output_filename}")
|
|
241
|
+
return df
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logging.warning(f"Issue processing file {filename}: {e}")
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _process_file_and_export(
|
|
248
|
+
config: Config,
|
|
249
|
+
config_name: str,
|
|
250
|
+
current_date: str,
|
|
251
|
+
custom_folder_path: str,
|
|
252
|
+
data_output_filetype: DataOutputFiletype,
|
|
253
|
+
file_path: str,
|
|
254
|
+
filename: str,
|
|
255
|
+
keep_all_additional_data: bool,
|
|
256
|
+
output_path: str,
|
|
257
|
+
) -> None:
|
|
258
|
+
"""
|
|
259
|
+
Process a single file and save the standardized DataFrame to the given output_path.
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
config : Config
|
|
264
|
+
The configuration to use for standardizing the file.
|
|
265
|
+
config_name : str
|
|
266
|
+
The name of the configuration.
|
|
267
|
+
current_date : str
|
|
268
|
+
The current date in the format %Y-%m-%d_%H-%M-%S.
|
|
269
|
+
custom_folder_path : str
|
|
270
|
+
The path to the directory containing the custom reader, mapper, and
|
|
271
|
+
formatter reference for the given configuration.
|
|
272
|
+
data_output_filetype : DataOutputFiletype
|
|
273
|
+
The file type to use when exporting the data.
|
|
274
|
+
file_path : str
|
|
275
|
+
The path to the file to standardize.
|
|
276
|
+
filename : str
|
|
277
|
+
The name of the file to standardize.
|
|
278
|
+
keep_all_additional_data : bool
|
|
279
|
+
Whether to keep all additional data in the output DataFrame.
|
|
280
|
+
output_path : str
|
|
281
|
+
The path to the directory where the standardized file will be written.
|
|
282
|
+
"""
|
|
283
|
+
logging.info(f"Processing file: {filename}")
|
|
284
|
+
try:
|
|
285
|
+
df = convert_file(config, file_path, keep_all_additional_data, custom_folder_path)
|
|
286
|
+
output_filename = f"{os.path.splitext(filename)[0]}_{config_name}_{current_date}"
|
|
287
|
+
write(df, output_path, output_filename, data_output_filetype)
|
|
288
|
+
logging.info(f"Successfully processed and exported: {output_filename}")
|
|
289
|
+
except Exception as e:
|
|
290
|
+
logging.warning(f"Issue processing file {filename}: {e}")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _convert_file_to_pandas_data_frame(
|
|
294
|
+
config: Config,
|
|
295
|
+
input_path: str,
|
|
296
|
+
custom_folder: str = None,
|
|
297
|
+
) -> tuple[pd.DataFrame, str]:
|
|
298
|
+
"""
|
|
299
|
+
Convert a file to a pandas DataFrame using the given configuration.
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
config : Config
|
|
304
|
+
The configuration to use for converting the file.
|
|
305
|
+
input_path : str
|
|
306
|
+
The path to the file to convert.
|
|
307
|
+
custom_folder : str, optional
|
|
308
|
+
The path to the directory containing the custom reader, mapper, and
|
|
309
|
+
formatter reference for the given configuration.
|
|
310
|
+
|
|
311
|
+
Returns
|
|
312
|
+
-------
|
|
313
|
+
tuple[DataFrame, str]
|
|
314
|
+
A tuple containing the converted DataFrame and the meta data.
|
|
315
|
+
"""
|
|
316
|
+
logging.info("converting file to pandas DataFrame...")
|
|
317
|
+
|
|
318
|
+
if Config.not_exists(config):
|
|
319
|
+
raise ValueError(f"Unknown config: {config}")
|
|
320
|
+
|
|
321
|
+
df = None
|
|
322
|
+
meta_data = ""
|
|
323
|
+
if config == Config.Custom:
|
|
324
|
+
if ExtPath.is_not_valid(custom_folder):
|
|
325
|
+
raise ValueError(f"Custom folder path must be provided for {Config.Custom}!")
|
|
326
|
+
|
|
327
|
+
custom_reader = load_custom_module(custom_folder, "Reader")
|
|
328
|
+
if custom_reader.to_data_frame is None:
|
|
329
|
+
raise ValueError("to_data_frame in custom reader is None.")
|
|
330
|
+
|
|
331
|
+
df, meta_data = custom_reader.to_data_frame(input_path)
|
|
332
|
+
elif config in READER_CONFIGS:
|
|
333
|
+
df, meta_data = READER_CONFIGS[config](input_path)
|
|
334
|
+
|
|
335
|
+
return df, meta_data
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _column_mapping(
|
|
339
|
+
df: pd.DataFrame,
|
|
340
|
+
config: Config,
|
|
341
|
+
custom_folder: str = None,
|
|
342
|
+
) -> pd.DataFrame:
|
|
343
|
+
"""
|
|
344
|
+
Map the columns of a pandas DataFrame according to the given configuration.
|
|
345
|
+
|
|
346
|
+
Parameters
|
|
347
|
+
----------
|
|
348
|
+
data_frame : DataFrame
|
|
349
|
+
The DataFrame to map the columns of.
|
|
350
|
+
config : Config
|
|
351
|
+
The configuration to use for mapping the columns.
|
|
352
|
+
custom_folder : str, optional
|
|
353
|
+
The path to the directory containing the custom mapper module for the given configuration.
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
DataFrame
|
|
358
|
+
The DataFrame with the mapped columns.
|
|
359
|
+
"""
|
|
360
|
+
logging.info("mapping columns...")
|
|
361
|
+
|
|
362
|
+
if df is None:
|
|
363
|
+
raise ValueError("Data frame is None.")
|
|
364
|
+
if Config.not_exists(config):
|
|
365
|
+
raise ValueError(f"Unknown config: {config}")
|
|
366
|
+
|
|
367
|
+
if config in MAPPER_CONFIGS:
|
|
368
|
+
column_map, missing_required_columns = MAPPER_CONFIGS[config]
|
|
369
|
+
return mapping(df, column_map, missing_required_columns)
|
|
370
|
+
elif config == Config.Custom:
|
|
371
|
+
if ExtPath.is_not_valid(custom_folder):
|
|
372
|
+
raise ValueError(f"Custom folder path must be provided for {Config.Custom}!")
|
|
373
|
+
|
|
374
|
+
custom_mapper = load_custom_module(custom_folder, "Mapper")
|
|
375
|
+
|
|
376
|
+
if custom_mapper.COLUMN_MAP is None:
|
|
377
|
+
raise ValueError("COLUMN_MAP in custom mapper is None.")
|
|
378
|
+
if custom_mapper.MISSING_REQUIRED_COLUMNS is None:
|
|
379
|
+
raise ValueError("MISSING_REQUIRED_COLUMNS in custom mapper is None.")
|
|
380
|
+
|
|
381
|
+
return mapping(df, custom_mapper.COLUMN_MAP, custom_mapper.MISSING_REQUIRED_COLUMNS)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _drop_additional_data(df: pd.DataFrame) -> pd.DataFrame:
|
|
385
|
+
"""
|
|
386
|
+
Drop columns that are not in STANDARD_COLUMNS from a DataFrame.
|
|
387
|
+
|
|
388
|
+
Parameters
|
|
389
|
+
----------
|
|
390
|
+
data_frame : DataFrame
|
|
391
|
+
The DataFrame to drop columns from.
|
|
392
|
+
|
|
393
|
+
Returns
|
|
394
|
+
-------
|
|
395
|
+
DataFrame
|
|
396
|
+
The DataFrame with columns dropped.
|
|
397
|
+
"""
|
|
398
|
+
logging.info("dropping additional data...")
|
|
399
|
+
|
|
400
|
+
if df is None:
|
|
401
|
+
raise ValueError("Data frame is None.")
|
|
402
|
+
|
|
403
|
+
return df[[col for col in df.columns if col in STANDARD_COLUMNS]]
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _add_metadata_to_dataframe(
|
|
407
|
+
df: pd.DataFrame,
|
|
408
|
+
meta_data: str,
|
|
409
|
+
) -> pd.DataFrame:
|
|
410
|
+
"""
|
|
411
|
+
Add the given meta data as a column in the DataFrame.
|
|
412
|
+
|
|
413
|
+
The first row of the "Meta_Data" column is set to the given meta data. All
|
|
414
|
+
other rows are set to None.
|
|
415
|
+
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
data_frame : DataFrame
|
|
419
|
+
The DataFrame to add the meta data to.
|
|
420
|
+
meta_data : str
|
|
421
|
+
The meta data to add as a column.
|
|
422
|
+
|
|
423
|
+
Returns
|
|
424
|
+
-------
|
|
425
|
+
DataFrame
|
|
426
|
+
The modified DataFrame with the added "Meta_Data" column.
|
|
427
|
+
"""
|
|
428
|
+
logging.info("adding metadata to Dataframe...")
|
|
429
|
+
|
|
430
|
+
if df is None:
|
|
431
|
+
raise ValueError("Data frame is None.")
|
|
432
|
+
|
|
433
|
+
df["Meta_Data"] = None # NO, this is faster then .loc
|
|
434
|
+
df.loc[0, "Meta_Data"] = str(meta_data)
|
|
435
|
+
return df
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _reorder_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
439
|
+
"""
|
|
440
|
+
Reorder the columns of a DataFrame to ensure standard columns are prioritized.
|
|
441
|
+
|
|
442
|
+
This function reorders the columns of the given DataFrame by first checking
|
|
443
|
+
and renaming any duplicate extra columns. It then selects the standard columns
|
|
444
|
+
in their defined order, followed by any extra columns in their original order.
|
|
445
|
+
The reordered DataFrame is then returned.
|
|
446
|
+
|
|
447
|
+
Parameters
|
|
448
|
+
----------
|
|
449
|
+
data_frame : DataFrame
|
|
450
|
+
The DataFrame whose columns need to be reordered.
|
|
451
|
+
|
|
452
|
+
Returns
|
|
453
|
+
-------
|
|
454
|
+
DataFrame
|
|
455
|
+
The DataFrame with columns reordered to have standard columns first, followed
|
|
456
|
+
by extra columns.
|
|
457
|
+
|
|
458
|
+
Raises
|
|
459
|
+
------
|
|
460
|
+
ValueError
|
|
461
|
+
If the data_frame is None or empty.
|
|
462
|
+
"""
|
|
463
|
+
logging.info("Getting columns in correct order...")
|
|
464
|
+
|
|
465
|
+
if df is None:
|
|
466
|
+
raise ValueError("Data frame is None.")
|
|
467
|
+
|
|
468
|
+
if df.columns.size == 0:
|
|
469
|
+
raise ValueError("Data frame is empty or has no columns.")
|
|
470
|
+
|
|
471
|
+
logging.info("Checking for duplicate extra columns...")
|
|
472
|
+
duplicates_fixed = _rename_duplicate_extra_columns(df.columns)
|
|
473
|
+
if not df.columns.equals(duplicates_fixed):
|
|
474
|
+
df.columns = duplicates_fixed
|
|
475
|
+
|
|
476
|
+
logging.info("Selecting and ordering standard columns...")
|
|
477
|
+
ordered_standard_columns = [col for col in STANDARD_COLUMNS if col in df.columns]
|
|
478
|
+
|
|
479
|
+
logging.info("Selecting extra columns...")
|
|
480
|
+
extra_columns = [col for col in df.columns if col not in STANDARD_COLUMNS]
|
|
481
|
+
|
|
482
|
+
logging.info("Combining standard and extra columns...")
|
|
483
|
+
ordered_columns = ordered_standard_columns + extra_columns
|
|
484
|
+
|
|
485
|
+
df_reordered = df[ordered_columns]
|
|
486
|
+
logging.info("Reordered DataFrame columns!")
|
|
487
|
+
|
|
488
|
+
return df_reordered
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _rename_duplicate_extra_columns(columns: Index) -> Index:
|
|
492
|
+
"""
|
|
493
|
+
Rename columns that are not in STANDARD_COLUMNS to ensure all columns have unique names.
|
|
494
|
+
|
|
495
|
+
Parameters
|
|
496
|
+
----------
|
|
497
|
+
columns : Index
|
|
498
|
+
The Index of column names to modify.
|
|
499
|
+
|
|
500
|
+
Returns
|
|
501
|
+
-------
|
|
502
|
+
Index
|
|
503
|
+
The modified Index with any duplicate non-standard columns renamed.
|
|
504
|
+
|
|
505
|
+
Notes
|
|
506
|
+
-----
|
|
507
|
+
Duplicates are renamed by appending "_<count>" where <count> is the number of
|
|
508
|
+
times that column name has appeared before.
|
|
509
|
+
"""
|
|
510
|
+
result = columns.to_list()
|
|
511
|
+
duplicate_counts = {}
|
|
512
|
+
|
|
513
|
+
for idx, col_name in enumerate(result):
|
|
514
|
+
if col_name not in STANDARD_COLUMNS:
|
|
515
|
+
count = duplicate_counts.setdefault(col_name, 0)
|
|
516
|
+
if count > 0:
|
|
517
|
+
result[idx] = f"{col_name}_{count}"
|
|
518
|
+
duplicate_counts[col_name] += 1
|
|
519
|
+
|
|
520
|
+
if any(count > 0 for count in duplicate_counts.values()):
|
|
521
|
+
logging.warning(
|
|
522
|
+
"Duplicate non-standard-columns were detected and renamed (by appending numbers) to ensure unique column names."
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
return Index(result)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _get_data_into_format(
|
|
529
|
+
df: pd.DataFrame,
|
|
530
|
+
config: Config,
|
|
531
|
+
custom_folder: str = None,
|
|
532
|
+
) -> pd.DataFrame:
|
|
533
|
+
"""
|
|
534
|
+
Apply the appropriate data formatting function to a DataFrame based on the given configuration.
|
|
535
|
+
|
|
536
|
+
This function modifies the format of the data in the DataFrame according to the
|
|
537
|
+
specified configuration. If the configuration is set to custom, it loads a custom
|
|
538
|
+
formatter module from the provided directory and applies it. Otherwise, it uses
|
|
539
|
+
a predefined formatter function based on the configuration.
|
|
540
|
+
|
|
541
|
+
Parameters
|
|
542
|
+
----------
|
|
543
|
+
data_frame : DataFrame
|
|
544
|
+
The DataFrame whose data needs to be formatted.
|
|
545
|
+
config : Config
|
|
546
|
+
The configuration determining which formatter to use.
|
|
547
|
+
custom_folder : str, optional
|
|
548
|
+
The path to the directory containing the custom formatter module if the
|
|
549
|
+
configuration is set to custom.
|
|
550
|
+
|
|
551
|
+
Returns
|
|
552
|
+
-------
|
|
553
|
+
DataFrame
|
|
554
|
+
The DataFrame with its data formatted according to the specified configuration.
|
|
555
|
+
|
|
556
|
+
Raises
|
|
557
|
+
------
|
|
558
|
+
ValueError
|
|
559
|
+
If the data_frame is None, config is None, or if the config is unknown.
|
|
560
|
+
If the custom_folder path is invalid for a custom config.
|
|
561
|
+
If there is an error loading or applying the custom formatter module.
|
|
562
|
+
If there is an error applying the predefined formatter.
|
|
563
|
+
"""
|
|
564
|
+
logging.info("Starting to fix data format...")
|
|
565
|
+
|
|
566
|
+
if df is None:
|
|
567
|
+
raise ValueError("Data frame is None.")
|
|
568
|
+
if config is None:
|
|
569
|
+
raise ValueError("Config is None.")
|
|
570
|
+
if config not in FORMATTER_CONFIGS and config != Config.Custom:
|
|
571
|
+
raise ValueError(f"Unknown config: {config}")
|
|
572
|
+
if ExtPath.is_not_valid(custom_folder) and config == Config.Custom:
|
|
573
|
+
raise ValueError(f"Valid custom folder path must be provided for {Config.Custom}")
|
|
574
|
+
|
|
575
|
+
if config == Config.Custom:
|
|
576
|
+
logging.info("Loading custom formatter module...")
|
|
577
|
+
try:
|
|
578
|
+
custom_formatter = load_custom_module(custom_folder, "Formatter")
|
|
579
|
+
except Exception as e:
|
|
580
|
+
raise ValueError(f"Error loading custom formatter module: {e}") from e
|
|
581
|
+
try:
|
|
582
|
+
df = custom_formatter.get_data_into_format(df)
|
|
583
|
+
except Exception as e:
|
|
584
|
+
raise ValueError(f"Error applying custom formatter: {e}") from e
|
|
585
|
+
else:
|
|
586
|
+
logging.info(f"Using formatter for config: {config}")
|
|
587
|
+
try:
|
|
588
|
+
FORMATTER_CONFIGS[config](df)
|
|
589
|
+
except Exception as e:
|
|
590
|
+
raise ValueError(f"Error applying formatter: {e}") from e
|
|
591
|
+
|
|
592
|
+
logging.info("Data format fixed.")
|
|
593
|
+
|
|
594
|
+
return df
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from pydpeet.io.utils.formatter_utils import round_testtime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_data_into_format(df: pd.DataFrame) -> pd.DataFrame:
|
|
7
|
+
"""
|
|
8
|
+
Rounds the values in the "Test_Time[s]" column of the DataFrame to 5 decimal places.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
dataFrame : pandas.DataFrame
|
|
13
|
+
DataFrame containing the "Test_Time[s]" column to be rounded.
|
|
14
|
+
|
|
15
|
+
Returns
|
|
16
|
+
-------
|
|
17
|
+
pandas.DataFrame
|
|
18
|
+
Modified DataFrame with rounded "Test_Time[s]" values.
|
|
19
|
+
"""
|
|
20
|
+
df = round_testtime(df)
|
|
21
|
+
|
|
22
|
+
return df
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Map raw-data column names (left) to standardized column names (right)
|
|
2
|
+
COLUMN_MAP = {
|
|
3
|
+
"Data_Point": "Step_Count",
|
|
4
|
+
"Voltage(V)": "Voltage[V]",
|
|
5
|
+
"Current(A)": "Current[A]",
|
|
6
|
+
"Test_Time(s)": "Test_Time[s]",
|
|
7
|
+
"Date_Time": "Date_Time",
|
|
8
|
+
"Temperature (C)_1": "Temperature[°C]",
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
# Default columns of the standardized format
|
|
12
|
+
# which are not present in the raw data files.
|
|
13
|
+
MISSING_REQUIRED_COLUMNS = [
|
|
14
|
+
"EIS_f[Hz]",
|
|
15
|
+
"EIS_Z_Real[Ohm]",
|
|
16
|
+
"EIS_Z_Imag[Ohm]",
|
|
17
|
+
"EIS_DC[A]",
|
|
18
|
+
]
|