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
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,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,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,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
+ ]