roc-film 1.13.5__py3-none-any.whl → 1.14.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.
- roc/__init__.py +2 -1
- roc/film/__init__.py +2 -2
- roc/film/commands.py +372 -323
- roc/film/config/__init__.py +0 -1
- roc/film/constants.py +101 -65
- roc/film/descriptor.json +127 -96
- roc/film/exceptions.py +28 -27
- roc/film/tasks/__init__.py +16 -16
- roc/film/tasks/cat_solo_hk.py +86 -74
- roc/film/tasks/cdf_postpro.py +438 -309
- roc/film/tasks/check_dds.py +39 -45
- roc/film/tasks/db_to_anc_bia_sweep_table.py +381 -0
- roc/film/tasks/dds_to_l0.py +232 -180
- roc/film/tasks/export_solo_coord.py +147 -0
- roc/film/tasks/file_handler.py +91 -75
- roc/film/tasks/l0_to_hk.py +117 -103
- roc/film/tasks/l0_to_l1_bia_current.py +38 -30
- roc/film/tasks/l0_to_l1_bia_sweep.py +417 -329
- roc/film/tasks/l0_to_l1_sbm.py +250 -208
- roc/film/tasks/l0_to_l1_surv.py +185 -130
- roc/film/tasks/make_daily_tm.py +40 -37
- roc/film/tasks/merge_tcreport.py +77 -71
- roc/film/tasks/merge_tmraw.py +101 -88
- roc/film/tasks/parse_dds_xml.py +21 -20
- roc/film/tasks/set_l0_utc.py +51 -49
- roc/film/tests/cdf_compare.py +565 -0
- roc/film/tests/hdf5_compare.py +84 -62
- roc/film/tests/test_dds_to_l0.py +93 -51
- roc/film/tests/test_dds_to_tc.py +8 -11
- roc/film/tests/test_dds_to_tm.py +8 -10
- roc/film/tests/test_film.py +161 -116
- roc/film/tests/test_l0_to_hk.py +64 -36
- roc/film/tests/test_l0_to_l1_bia.py +10 -14
- roc/film/tests/test_l0_to_l1_sbm.py +14 -19
- roc/film/tests/test_l0_to_l1_surv.py +68 -41
- roc/film/tests/test_metadata.py +21 -20
- roc/film/tests/tests.py +743 -396
- roc/film/tools/__init__.py +5 -5
- roc/film/tools/dataset_tasks.py +34 -2
- roc/film/tools/file_helpers.py +390 -269
- roc/film/tools/l0.py +402 -324
- roc/film/tools/metadata.py +147 -127
- roc/film/tools/skeleton.py +12 -17
- roc/film/tools/tools.py +109 -92
- roc/film/tools/xlsx2skt.py +161 -139
- {roc_film-1.13.5.dist-info → roc_film-1.14.0.dist-info}/LICENSE +127 -125
- roc_film-1.14.0.dist-info/METADATA +60 -0
- roc_film-1.14.0.dist-info/RECORD +50 -0
- {roc_film-1.13.5.dist-info → roc_film-1.14.0.dist-info}/WHEEL +1 -1
- roc/film/tasks/l0_to_anc_bia_sweep_table.py +0 -348
- roc_film-1.13.5.dist-info/METADATA +0 -120
- roc_film-1.13.5.dist-info/RECORD +0 -48
roc/film/tools/tools.py
CHANGED
@@ -17,32 +17,38 @@ from poppy.core.generic.metaclasses import Singleton
|
|
17
17
|
from poppy.core.configuration import Configuration
|
18
18
|
from poppy.core.generic.paths import Paths
|
19
19
|
from poppy.core.logger import logger
|
20
|
-
from roc.film.constants import
|
21
|
-
|
20
|
+
from roc.film.constants import (
|
21
|
+
_ROOT_DIRECTORY,
|
22
|
+
INPUT_DATETIME_STRFTIME,
|
23
|
+
DATA_VERSION,
|
24
|
+
TIME_DAILY_STRFORMAT,
|
25
|
+
)
|
22
26
|
|
23
27
|
from roc.film.exceptions import FilmException, HandlingFileError
|
24
28
|
|
25
|
-
__all__ = [
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
29
|
+
__all__ = [
|
30
|
+
"paths",
|
31
|
+
"DESCRIPTOR",
|
32
|
+
"raise_error",
|
33
|
+
"valid_time",
|
34
|
+
"valid_date",
|
35
|
+
"valid_data_version",
|
36
|
+
"valid_single_file",
|
37
|
+
"valid_dir",
|
38
|
+
"extract_datetime",
|
39
|
+
"extract_file_fields",
|
40
|
+
"get_datasets",
|
41
|
+
"sort_indices",
|
42
|
+
"unique_dict_list",
|
43
|
+
"sort_dict_list",
|
44
|
+
"safe_move",
|
45
|
+
"setup_lock",
|
46
|
+
"get_latest_file",
|
47
|
+
"Map",
|
48
|
+
"glob_list",
|
49
|
+
"move_to_trash",
|
50
|
+
"decode",
|
51
|
+
]
|
46
52
|
|
47
53
|
# ________________ Global Variables _____________
|
48
54
|
# (define here the global variables)
|
@@ -65,20 +71,18 @@ def get_descriptor():
|
|
65
71
|
"""
|
66
72
|
|
67
73
|
class Descriptor(object, metaclass=Singleton):
|
68
|
-
|
69
74
|
def __init__(self):
|
70
|
-
|
71
|
-
descriptor = paths.from_root('descriptor.json')
|
75
|
+
descriptor = paths.from_root("descriptor.json")
|
72
76
|
|
73
77
|
# Get descriptor content
|
74
|
-
with open(descriptor,
|
78
|
+
with open(descriptor, "r") as file_buffer:
|
75
79
|
for key, val in json.load(file_buffer).items():
|
76
80
|
setattr(self, key, val)
|
77
81
|
|
78
82
|
# Re-organize task section
|
79
83
|
tasks = dict()
|
80
84
|
for task in self.tasks:
|
81
|
-
tasks[task[
|
85
|
+
tasks[task["name"]] = task
|
82
86
|
|
83
87
|
self.tasks = tasks
|
84
88
|
|
@@ -107,8 +111,9 @@ def valid_time(t, format=INPUT_DATETIME_STRFTIME):
|
|
107
111
|
try:
|
108
112
|
return datetime.strptime(t, format)
|
109
113
|
except ValueError:
|
110
|
-
raise_error(
|
111
|
-
|
114
|
+
raise_error(
|
115
|
+
f"Not a valid datetime: '{t}'.", exception=argparse.ArgumentTypeError
|
116
|
+
)
|
112
117
|
|
113
118
|
|
114
119
|
def valid_date(t, format=TIME_DAILY_STRFORMAT):
|
@@ -123,8 +128,9 @@ def valid_date(t, format=TIME_DAILY_STRFORMAT):
|
|
123
128
|
try:
|
124
129
|
return datetime.strptime(t, format)
|
125
130
|
except ValueError:
|
126
|
-
raise_error(
|
127
|
-
|
131
|
+
raise_error(
|
132
|
+
f"Not a valid date: '{t}'.", exception=argparse.ArgumentTypeError
|
133
|
+
)
|
128
134
|
|
129
135
|
|
130
136
|
def valid_data_version(data_version):
|
@@ -138,9 +144,9 @@ def valid_data_version(data_version):
|
|
138
144
|
if isinstance(data_version, list):
|
139
145
|
data_version = data_version[0]
|
140
146
|
data_version = int(data_version)
|
141
|
-
return f
|
147
|
+
return f"{data_version:02d}"
|
142
148
|
except ValueError:
|
143
|
-
raise_error(f
|
149
|
+
raise_error(f"Input value for --data-version is not valid! ({data_version})")
|
144
150
|
|
145
151
|
|
146
152
|
def valid_single_file(file):
|
@@ -158,11 +164,11 @@ def valid_single_file(file):
|
|
158
164
|
else:
|
159
165
|
raise FileNotFoundError
|
160
166
|
except FileNotFoundError:
|
161
|
-
raise_error(f
|
167
|
+
raise_error(f"Input file not found! ({file})", exception=FileNotFoundError)
|
162
168
|
except ValueError:
|
163
|
-
raise_error(f
|
169
|
+
raise_error(f"Input file is not valid! ({file})", exception=ValueError)
|
164
170
|
except Exception as e:
|
165
|
-
raise_error(f
|
171
|
+
raise_error(f"Problem with input file! ({file})", exception=e)
|
166
172
|
|
167
173
|
|
168
174
|
def valid_dir(dir):
|
@@ -180,11 +186,11 @@ def valid_dir(dir):
|
|
180
186
|
else:
|
181
187
|
raise IsADirectoryError
|
182
188
|
except IsADirectoryError:
|
183
|
-
raise_error(f
|
189
|
+
raise_error(f"Input directory not found! ({dir})", exception=IsADirectoryError)
|
184
190
|
except ValueError:
|
185
|
-
raise_error(f
|
191
|
+
raise_error(f"Input directory is not valid! ({dir})", exception=ValueError)
|
186
192
|
except Exception as e:
|
187
|
-
raise_error(f
|
193
|
+
raise_error(f"Problem with input directory! ({dir})", exception=e)
|
188
194
|
|
189
195
|
|
190
196
|
def unique_dates(utc_times):
|
@@ -212,14 +218,15 @@ def extract_datetime(str_datetime):
|
|
212
218
|
:return: 2-elements list containing Datetime start/end time (if input Datetime has a daily format, return the day twice).
|
213
219
|
"""
|
214
220
|
|
215
|
-
str_datetime_list = str_datetime.split(
|
221
|
+
str_datetime_list = str_datetime.split("-")
|
216
222
|
if len(str_datetime_list) == 1:
|
217
|
-
out_datetime = [datetime.strptime(str_datetime,
|
223
|
+
out_datetime = [datetime.strptime(str_datetime, "%Y%m%d")] * 2
|
218
224
|
elif len(str_datetime_list) == 2:
|
219
|
-
out_datetime = [
|
220
|
-
dt,
|
225
|
+
out_datetime = [
|
226
|
+
datetime.strptime(dt, "%Y%m%dT%H%M%S") for dt in str_datetime_list
|
227
|
+
]
|
221
228
|
else:
|
222
|
-
logger.error(f
|
229
|
+
logger.error(f"Wrong input datetime format: {str_datetime}")
|
223
230
|
return None
|
224
231
|
|
225
232
|
return out_datetime
|
@@ -251,30 +258,29 @@ def get_datasets(task, task_name):
|
|
251
258
|
"""
|
252
259
|
|
253
260
|
# Get dataset JSON file provided as an input argument (if any)
|
254
|
-
dataset_file = task.pipeline.get(
|
255
|
-
'dataset_file', default=[None], args=True)[0]
|
261
|
+
dataset_file = task.pipeline.get("dataset_file", default=[None], args=True)[0]
|
256
262
|
# Get --dataset input keyword value (if any)
|
257
|
-
dataset_names = task.pipeline.get(
|
263
|
+
dataset_names = task.pipeline.get("dataset", default=[None], args=True)
|
258
264
|
# Get --data-version input keyword value (if any)
|
259
|
-
data_version = task.pipeline.get(
|
260
|
-
|
265
|
+
data_version = task.pipeline.get("data_version", default=[DATA_VERSION], args=True)[
|
266
|
+
0
|
267
|
+
]
|
261
268
|
|
262
269
|
# Get task output dataset description list from the descriptor.json file
|
263
|
-
task_output_list = DESCRIPTOR.tasks[task_name][
|
270
|
+
task_output_list = DESCRIPTOR.tasks[task_name]["outputs"]
|
264
271
|
|
265
272
|
# If dataset JSON file passed as an value of the --dataset_file input
|
266
273
|
# keyword, load list of datasets to create and related data_version
|
267
274
|
# (optional)
|
268
275
|
if dataset_file and os.path.isfile(dataset_file):
|
269
|
-
with open(dataset_file,
|
276
|
+
with open(dataset_file, "r") as file_buff:
|
270
277
|
# Loop over dataset array in the JSON file to get the name and
|
271
278
|
# optionally the version of the output file
|
272
279
|
dataset_to_create = []
|
273
280
|
data_versions = []
|
274
|
-
for current_dataset_obj in json.load(file_buff)[
|
275
|
-
dataset_to_create.append(current_dataset_obj[
|
276
|
-
data_versions.append(
|
277
|
-
current_dataset_obj.get('version', data_version))
|
281
|
+
for current_dataset_obj in json.load(file_buff)["dataset"]:
|
282
|
+
dataset_to_create.append(current_dataset_obj["name"])
|
283
|
+
data_versions.append(current_dataset_obj.get("version", data_version))
|
278
284
|
elif dataset_names[0]:
|
279
285
|
# Else if dataset list passed as values of the --dataset input keyword
|
280
286
|
dataset_to_create = dataset_names
|
@@ -282,22 +288,25 @@ def get_datasets(task, task_name):
|
|
282
288
|
else:
|
283
289
|
# Else load all the output datasets listed in descriptor for the given
|
284
290
|
# task by default
|
285
|
-
dataset_to_create = list(DESCRIPTOR.tasks[task_name][
|
291
|
+
dataset_to_create = list(DESCRIPTOR.tasks[task_name]["outputs"].keys())
|
286
292
|
data_versions = [data_version] * len(dataset_to_create)
|
287
293
|
|
288
294
|
# Retrieve dataset info from descriptor.json
|
289
295
|
dataset_list = []
|
290
296
|
for i, dataset_name in enumerate(dataset_to_create):
|
291
|
-
|
292
297
|
# Check if current dataset is indeed a output dataset of the task (as
|
293
298
|
# described in the descriptor.json file)
|
294
299
|
if dataset_name not in task_output_list:
|
295
|
-
logger.warning(
|
300
|
+
logger.warning(
|
301
|
+
f"{dataset_name} is not a valid dataset of the task {task_name}!"
|
302
|
+
)
|
296
303
|
else:
|
297
304
|
# if yes, get description and store info in the dataset_list list
|
298
|
-
current_dataset = {
|
299
|
-
|
300
|
-
|
305
|
+
current_dataset = {
|
306
|
+
"name": dataset_name,
|
307
|
+
"version": data_versions[i],
|
308
|
+
"descr": task_output_list[dataset_name],
|
309
|
+
}
|
301
310
|
dataset_list.append(current_dataset)
|
302
311
|
|
303
312
|
return dataset_list
|
@@ -310,7 +319,7 @@ def unique_dict_list(list_of_dict):
|
|
310
319
|
:param list_of_dict: List of dict to unify
|
311
320
|
:return: return list inside which each dict is unique
|
312
321
|
"""
|
313
|
-
return [i for n, i in enumerate(list_of_dict) if i not in list_of_dict[n + 1:]]
|
322
|
+
return [i for n, i in enumerate(list_of_dict) if i not in list_of_dict[n + 1 :]]
|
314
323
|
|
315
324
|
|
316
325
|
def sort_dict_list(list_of_dict, key):
|
@@ -333,8 +342,7 @@ def sort_indices(list_to_sort):
|
|
333
342
|
:return: list of sorted indices
|
334
343
|
"""
|
335
344
|
|
336
|
-
return sorted(range(len(list_to_sort)),
|
337
|
-
key=lambda k: list_to_sort[k])
|
345
|
+
return sorted(range(len(list_to_sort)), key=lambda k: list_to_sort[k])
|
338
346
|
|
339
347
|
|
340
348
|
def safe_move(src, dst, ignore_patterns=[]):
|
@@ -355,11 +363,14 @@ def safe_move(src, dst, ignore_patterns=[]):
|
|
355
363
|
if os.path.isfile(src):
|
356
364
|
shutil.copy(src, dst, follow_symlinks=True)
|
357
365
|
elif os.path.isdir(src):
|
358
|
-
shutil.copytree(
|
359
|
-
|
360
|
-
|
366
|
+
shutil.copytree(
|
367
|
+
src,
|
368
|
+
dst,
|
369
|
+
ignore=shutil.ignore_patterns(ignore_patterns),
|
370
|
+
dirs_exist_ok=True,
|
371
|
+
)
|
361
372
|
except Exception:
|
362
|
-
raise HandlingFileError(f
|
373
|
+
raise HandlingFileError(f"Cannot move {src} into {dst}!")
|
363
374
|
else:
|
364
375
|
# then delete if the file has well copied
|
365
376
|
if os.path.exists(dst):
|
@@ -382,10 +393,11 @@ def setup_lock(pipeline):
|
|
382
393
|
"""
|
383
394
|
|
384
395
|
# Retrieve lock_file input argument value
|
385
|
-
lock_file = pipeline.get(
|
396
|
+
lock_file = pipeline.get("lock_file", default=[None], args=True)[0]
|
386
397
|
if lock_file is not None:
|
387
398
|
# Retrieve output directory path
|
388
399
|
from roc.film.tools.file_helpers import get_output_dir
|
400
|
+
|
389
401
|
output_dir = get_output_dir(pipeline)
|
390
402
|
|
391
403
|
# Set the value of Pipeline.lock attribute filename
|
@@ -403,9 +415,9 @@ def sort_cdf_by_epoch(cdf, descending=False, zvar_list=[]):
|
|
403
415
|
"""
|
404
416
|
|
405
417
|
try:
|
406
|
-
epoch = cdf[
|
418
|
+
epoch = cdf["Epoch"]
|
407
419
|
except Exception:
|
408
|
-
logger.error(
|
420
|
+
logger.error("Cannot get Epoch zVariable from input CDF!")
|
409
421
|
return cdf
|
410
422
|
|
411
423
|
sorted_idx = np.argsort(epoch[...])
|
@@ -417,18 +429,20 @@ def sort_cdf_by_epoch(cdf, descending=False, zvar_list=[]):
|
|
417
429
|
|
418
430
|
for zvar in zvar_list:
|
419
431
|
current_zvar = cdf[zvar][...]
|
420
|
-
current_zvar = current_zvar[sorted_idx,
|
432
|
+
current_zvar = current_zvar[sorted_idx,]
|
421
433
|
cdf[zvar] = current_zvar
|
422
434
|
|
423
435
|
return cdf
|
424
436
|
|
425
437
|
|
426
|
-
def extract_file_fields(
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
438
|
+
def extract_file_fields(
|
439
|
+
rpw_file,
|
440
|
+
get_source=False,
|
441
|
+
get_level=False,
|
442
|
+
get_descriptor=False,
|
443
|
+
get_datetime=False,
|
444
|
+
get_version=False,
|
445
|
+
):
|
432
446
|
"""
|
433
447
|
Extract RPW file fields (assuming SolO file naming standards)
|
434
448
|
|
@@ -440,7 +454,7 @@ def extract_file_fields(rpw_file,
|
|
440
454
|
:param get_version: return only data version field
|
441
455
|
:return: list of file fields (or a scalar with expected field)
|
442
456
|
"""
|
443
|
-
fields = os.path.splitext(os.path.basename(rpw_file))[0].split(
|
457
|
+
fields = os.path.splitext(os.path.basename(rpw_file))[0].split("_")
|
444
458
|
|
445
459
|
if len(fields) < 5:
|
446
460
|
logger.warning(f'Cannot extract file fields: invalid input file "{rpw_file}"!')
|
@@ -497,12 +511,14 @@ class Map(dict):
|
|
497
511
|
del self.__dict__[key]
|
498
512
|
|
499
513
|
|
500
|
-
def glob_list(list_of_files):
|
514
|
+
def glob_list(list_of_files: list) -> list:
|
501
515
|
"""
|
502
516
|
Perform glob.glob on a list of input files.
|
503
517
|
|
504
|
-
:param list_of_files: List of input files (
|
518
|
+
:param list_of_files: List of input files (strings)
|
519
|
+
:type:list
|
505
520
|
:return: list of files globbed
|
521
|
+
:rtype: list
|
506
522
|
"""
|
507
523
|
output_list = []
|
508
524
|
for current_file in list_of_files:
|
@@ -526,7 +542,7 @@ def move_to_trash(file_or_dir):
|
|
526
542
|
is_file = False
|
527
543
|
|
528
544
|
try:
|
529
|
-
logger.debug(f
|
545
|
+
logger.debug(f"Moving {file_or_dir} into {trash_dir}")
|
530
546
|
target_path = os.path.join(trash_dir, os.path.basename(file_or_dir))
|
531
547
|
shutil.copyfile(file_or_dir, target_path)
|
532
548
|
if os.path.exists(target_path):
|
@@ -536,7 +552,7 @@ def move_to_trash(file_or_dir):
|
|
536
552
|
else:
|
537
553
|
shutil.rmtree(file_or_dir)
|
538
554
|
except Exception:
|
539
|
-
logger.exception(f
|
555
|
+
logger.exception(f"Moving {file_or_dir} into {trash_dir} has failed!")
|
540
556
|
target_path = None
|
541
557
|
|
542
558
|
return target_path
|
@@ -551,18 +567,17 @@ def get_trash_dir():
|
|
551
567
|
"""
|
552
568
|
|
553
569
|
# Get trash folder path
|
554
|
-
if
|
555
|
-
trash_dir = Configuration.manager[
|
556
|
-
|
557
|
-
|
558
|
-
trash_dir = os.environ['ROC_PIP_TRASH_DIR']
|
570
|
+
if "ROC_PIP_TRASH_DIR" in Configuration.manager["pipeline"]["environment"]:
|
571
|
+
trash_dir = Configuration.manager["pipeline"]["environment.ROC_PIP_TRASH_DIR"]
|
572
|
+
elif "ROC_PIP_TRASH_DIR" in os.environ:
|
573
|
+
trash_dir = os.environ["ROC_PIP_TRASH_DIR"]
|
559
574
|
else:
|
560
|
-
raise MissingProperty(
|
575
|
+
raise MissingProperty("ROC_PIP_TRASH_DIR variable is not defined!")
|
561
576
|
|
562
577
|
return trash_dir
|
563
578
|
|
564
579
|
|
565
|
-
def decode(binary, encoding=
|
580
|
+
def decode(binary, encoding="UTF-8"):
|
566
581
|
"""
|
567
582
|
Decode input binary into string.
|
568
583
|
|
@@ -575,10 +590,12 @@ def decode(binary, encoding='UTF-8'):
|
|
575
590
|
elif isinstance(binary, list):
|
576
591
|
return [element.decode(encoding) for element in binary]
|
577
592
|
elif isinstance(binary, np.ndarray):
|
593
|
+
|
578
594
|
def f(x):
|
579
595
|
return x.decode(encoding)
|
596
|
+
|
580
597
|
return np.vectorize(f)(binary)
|
581
598
|
elif isinstance(binary, bytes):
|
582
599
|
return binary.decode(encoding)
|
583
600
|
else:
|
584
|
-
raise ValueError(f
|
601
|
+
raise ValueError(f"Input binary type ({type(binary)}) is not valid!")
|