"""
This module provides general utility functions.
"""
import os
from astropy.time import Time
import hermes_core
__all__ = ["create_science_filename", "parse_science_filename", "VALID_DATA_LEVELS"]
TIME_FORMAT_L0 = "%Y%j-%H%M%S"
TIME_FORMAT = "%Y%m%dT%H%M%S"
VALID_DATA_LEVELS = ["l0", "l1", "ql", "l2", "l3", "l4"]
FILENAME_EXTENSION = ".cdf"
[docs]
def create_science_filename(
instrument: str,
time: str,
level: str,
version: str,
mode: str = "",
descriptor: str = "",
test: bool = False,
):
"""Return a compliant filename. The format is defined as
hermes_{inst}_{mode}_{level}{test}_{descriptor}_{time}_v{version}.cdf
This format is only appropriate for data level >= 1.
Parameters
----------
instrument : `str`
The instrument name. Must be one of the following "eea", "nemesis", "merit", "spani"
time : `str` (in isot format) or ~astropy.time
The time
level : `str`
The data level. Must be one of the following "l0", "l1", "l2", "l3", "l4", "ql"
version : `str`
The file version which must be given as X.Y.Z
descriptor : `str`
An optional file descriptor.
mode : `str`
An optional instrument mode.
test : bool
Selects whether the file is a test file.
Returns
-------
filename : `str`
A CDF file name including the given parameters that matches the HERMES file naming conventions
Raises
------
ValueError: If the instrument is not recognized as one of the HERMES instruments
ValueError: If the data level is not recognized as one of the HERMES valid data levels
ValueError: If the data version does not match the HERMES data version formatting conventions
ValueError: If the data product descriptor or instrument mode do not match the HERMES formatting conventions
"""
test_str = ""
if isinstance(time, str):
time_str = Time(time, format="isot").strftime(TIME_FORMAT)
else:
time_str = time.strftime(TIME_FORMAT)
if instrument not in hermes_core.INST_NAMES:
raise ValueError(
f"Instrument, {instrument}, is not recognized. Must be one of {hermes_core.INST_NAMES}."
)
if level not in VALID_DATA_LEVELS[1:]:
raise ValueError(
f"Level, {level}, is not recognized. Must be one of {VALID_DATA_LEVELS[1:]}."
)
# check that version is in the right format with three parts
if len(version.split(".")) != 3:
raise ValueError(
f"Version, {version}, is not formatted correctly. Should be X.Y.Z"
)
# check that version has integers in each part
for item in version.split("."):
try:
int_value = int(item)
except ValueError:
raise ValueError(f"Version, {version}, is not all integers.")
if test is True:
test_str = "test"
# the parse_science_filename function depends on _ not being present elsewhere
if ("_" in mode) or ("_" in descriptor):
raise ValueError(
"The underscore symbol _ is not allowed in mode or descriptor."
)
filename = f"hermes_{hermes_core.INST_TO_SHORTNAME[instrument]}_{mode}_{level}{test_str}_{descriptor}_{time_str}_v{version}"
filename = filename.replace("__", "_") # reformat if mode or descriptor not given
return filename + FILENAME_EXTENSION
[docs]
def parse_science_filename(filepath: str) -> dict:
"""
Parses a science filename into its consitutient properties (instrument, mode, test, time, level, version, descriptor).
Parameters
----------
filepath: `str`
Fully specificied filepath of an input file
Returns
-------
result : `dict`
A dictionary with each property.
Raises
------
ValueError: If the file's mission name is not "HERMES"
ValueError: If the file's instreument name is not one of the HERMES instruments
ValueError: If the data level >0 for packet files
ValueError: If not a CDF File
"""
result = {
"instrument": None,
"mode": None,
"test": False,
"time": None,
"level": None,
"version": None,
"descriptor": None,
}
filename = os.path.basename(filepath)
file_name, file_ext = os.path.splitext(filename)
filename_components = file_name.split("_")
if filename_components[0] != hermes_core.MISSION_NAME:
raise ValueError(f"File {filename} not recognized. Not a valid mission name.")
if file_ext == ".bin":
if filename_components[1] not in hermes_core.INST_TARGETNAMES:
raise ValueError(
f"File {filename} not recognized. Not a valid target name."
)
offset = 1 if len(filename_components) > 5 else 0
if offset:
result["mode"] = filename_components[2]
if filename_components[2 + offset] != VALID_DATA_LEVELS[0]:
raise ValueError(
f"Data level {filename_components[2 + offset]} is not correct for this file extension."
)
else:
result["level"] = filename_components[2 + offset]
# reverse the dictionary to look up instrument name from the short name
from_shortname = {v: k for k, v in hermes_core.INST_TO_TARGETNAME.items()}
result["time"] = Time.strptime(filename_components[3 + offset], TIME_FORMAT_L0)
elif file_ext == ".cdf":
if filename_components[1] not in hermes_core.INST_SHORTNAMES:
raise ValueError(
"File {filename} not recognized. Not a valid instrument name."
)
# reverse the dictionary to look up instrument name from the short name
from_shortname = {v: k for k, v in hermes_core.INST_TO_SHORTNAME.items()}
result["time"] = Time.strptime(filename_components[-2], TIME_FORMAT)
# mode and descriptor are optional so need to figure out if one or both or none is included
if filename_components[2][0:2] not in VALID_DATA_LEVELS:
# if the first component is not data level then it is mode and the following is data level
result["mode"] = filename_components[2]
result["level"] = filename_components[3].replace("test", "")
if "test" in filename_components[3]:
result["test"] = True
if len(filename_components) == 7:
result["descriptor"] = filename_components[4]
else:
result["level"] = filename_components[2].replace("test", "")
if "test" in filename_components[2]:
result["test"] = True
if len(filename_components) == 6:
result["descriptor"] = filename_components[3]
else:
raise ValueError(f"File extension {file_ext} not recognized.")
result["instrument"] = from_shortname[filename_components[1]]
result["version"] = filename_components[-1][1:] # remove the v
return result