"""Utility functions for time unit conversion and file I/O."""
from typing import Literal
import numpy as np
from numpy.typing import ArrayLike, NDArray
# Type alias for supported time units
TimeUnit = Literal["s", "ms", "micros", "ns"]
[docs]
class Utilities:
"""Utilities class"""
list_of_time_units: list[str] = ["s", "ms", "micros", "ns"]
[docs]
@staticmethod
def convert_time_units(
data: ArrayLike | float | None = None,
from_units: TimeUnit = "micros",
to_units: TimeUnit = "s",
) -> NDArray[np.floating] | float:
"""convert the time units
Parameters:
* vdata: single data or array of value to convert
* from_units: default 'micros'. Must be either ['s','micros','ns']
* to_units: default 's'. Must be either ['s', 'micros', 'ns']
"""
if data is None:
raise ValueError("Please provide data to convert")
list_of_time_units = Utilities.list_of_time_units
if from_units not in list_of_time_units or to_units not in list_of_time_units:
raise ValueError("Units convertion not supported")
coeff = Utilities.get_time_conversion_coeff(from_units=from_units, to_units=to_units)
if type(data) is list:
data = np.array(data)
return data * coeff
[docs]
@staticmethod
def get_time_conversion_coeff(from_units: TimeUnit = "micros", to_units: TimeUnit = "s") -> float:
"""return the coefficient to use to convert from first units to second units
Arguments:
* from_units: default 'micros'. Must be in the list of list_of_time_units
* to_units: default 's'. Must be in the list of list_of_time_units
Returns:
* coefficient to apply to data to convert from first units to second units provided
Raises:
* ValueError: if any of the units is not supported
"""
if from_units not in Utilities.list_of_time_units or to_units not in Utilities.list_of_time_units:
raise ValueError("Units not supported")
if from_units == to_units:
return 1
if from_units == "s":
if to_units == "ms":
return 1.0e3
if to_units == "micros":
return 1.0e6
if to_units == "ns":
return 1.0e9
if from_units == "ms":
if to_units == "s":
return 1.0e-3
if to_units == "micros":
return 1.0e3
if to_units == "ns":
return 1.0e6
if from_units == "micros":
if to_units == "s":
return 1.0e-6
if to_units == "ms":
return 1.0e-3
if to_units == "ns":
return 1.0e3
if from_units == "ns":
if to_units == "s":
return 1.0e-9
if to_units == "ms":
return 1.0e-6
if to_units == "micros":
return 1.0e-3
# This should never be reached due to the validation above
return 1.0
[docs]
@staticmethod
def array_multiply_coeff(
data: ArrayLike | None = None,
coeff: float = 1,
) -> NDArray[np.floating]:
"""multiply each element of the array by the coeff
Parameters:
* data: array to apply coefficient on
* coeff: default value is 1. Coefficient to apply
Returns:
* data * coefficient
"""
if data is None:
raise ValueError("Give me at least something to multiply!")
final_data = np.array([])
for _item in data:
_value = float(_item) * float(coeff)
final_data = np.append(final_data, _value)
return final_data
[docs]
@staticmethod
def array_add_coeff(
data: ArrayLike | None = None,
coeff: float = 1.0,
) -> NDArray[np.floating]:
"""Add coefficient to each element of the array
Parameters:
* data: array to apply coefficient on
* coeff: default value is 1. Coefficient to apply
Returns:
* data + coefficient
"""
if data is None:
raise ValueError("Give ma at least something to add!")
final_data = np.array([])
for index in range(len(data)):
_item = data[index]
_value = _item + coeff
final_data = np.append(final_data, _value)
return final_data
[docs]
@staticmethod
def array_divide_array(
numerator: NDArray[np.floating] | None = None,
denominator: NDArray[np.floating] | None = None,
) -> NDArray[np.floating]:
"""Divide two arrays of the same size
Parameters:
* numerator: numpy array
* denominator: numpy array
Returns:
* numerator / denominator
Raises:
* ValueError if array do not have the same size
"""
if len(numerator) != len(denominator):
raise ValueError("Arrays do not have the same size!")
return numerator / denominator
[docs]
@staticmethod
def array_minus_array(
array1: NDArray[np.floating] | None = None,
array2: NDArray[np.floating] | None = None,
) -> NDArray[np.floating]:
"""Substract second array from first array provided
Parameters:
* array1: left side of the '-' operator
* array2: right side of the '-' operator
Returns:
* Array1 - Array2
Raises:
* ValueError if arrays do not have the same size
"""
if len(array1) != len(array2):
raise ValueError("Arrays do not have the same size!")
return array1 - array2
[docs]
@staticmethod
def load_csv(filename: str | None = None) -> list[float]:
"""Load a csv file and return its content
Parameters:
* filename: name of the csv file to load
Returns:
contents of the file as an array item for each line
Raise:
ValueError if format is wrong
"""
_input_file = filename
try:
with open(_input_file) as f:
_tof: list[float] = []
for _line in f:
if "#" in _line:
continue
_value = float(_line.strip())
_tof.append(_value)
return _tof
except OSError:
raise
except ValueError as e:
raise ValueError("Bad file format") from e
[docs]
@staticmethod
def load_ascii(filename: str | None = None, sep: str = "") -> NDArray[np.floating]:
"""Load an ascii file using the separator provided to separete the value in the same row
Parameters:
* filename: ascii full file name of file to load
* sep: default ' '. Separator to use to separate values in the same row
Returns:
Array of values
Raise:
ValueError if file does not exist or format is wrong
"""
_input_file = filename
try:
_final_array: NDArray[np.floating] = np.genfromtxt(_input_file, delimiter=sep)
return _final_array
except:
raise ValueError("Bad file format!")
[docs]
@staticmethod
def save_csv(
filename: str | None = None,
metadata: list[str] | None = None,
data: list | NDArray | None = None,
) -> None:
"""Create comma separated file (CSV)
Arguments:
* filename: name of output file
* metadata: metadata string array (will be placed at the top of the file with '#' in front)
* data: data float array"""
sep = ", "
with open(filename, "w") as f:
for _meta in metadata:
f.write("# " + _meta + "\n")
for _row in data:
if (type(_row) is np.ndarray) or (type(_row) is list):
_row_string = [str(x) for x in _row]
_row_format = sep.join(_row_string)
else:
_row_format = str(_row)
f.write(_row_format + "\n")