Source code for ooragan.util
import easygui as eg
import numpy as np
import pandas as pd
import re
from datetime import datetime
from numpy.typing import NDArray, ArrayLike
from typing import Optional
def choice(
title: Optional[str] = None,
msg: Optional[str] = None,
) -> bool | None:
"""
Function that opens a choice box for the user to choose between yes or no.
Returns True if yes, False if no.
"""
if title is None:
title = "Overwrite warning"
if msg is None:
msg = (
"Do you really want to save the figure? It could delete an existing figure."
)
user_choice = eg.ynbox(msg=msg, title=title)
return user_choice
[docs]
def str_to_time(time_string: str) -> float:
"""
Converts the time format from the MeaVis' HDF5 files into a datetime timestamp.
Parameters
----------
time_string : str
Timestamp string from MeaVis.
"""
fields = dict(re.findall(r"(tm_\w+)=(-?\d+)", time_string))
dt = datetime(
year=int(fields["tm_year"]),
month=int(fields["tm_mon"]),
day=int(fields["tm_mday"]),
hour=int(fields["tm_hour"]),
minute=int(fields["tm_min"]),
second=int(fields["tm_sec"]),
)
return dt.timestamp()
[docs]
def convert_magphase_to_complex(
mag: NDArray, phase: NDArray, deg: bool = True, dBm: bool = True
) -> tuple[NDArray, NDArray]:
r"""
Converts magnitude and phase data into real and imaginary.
Parameters
----------
mag : NDArray
Magnitude array.
phase : NDArray
Phase array
deg : bool, optional
Set to ``True`` if the phase is in degrees. Defaults to ``True``.
dBm : bool, optional
Set to ``True`` if the magnitude is in dBm. Defaults to ``True``.
Notes
-----
This conversion is defined as
.. math::
S_{21}^\text{complex} = 10^{\frac{|S_{21}|}{20}}e^{i\phi}
where the magnitude :math:`|S_{21}|` is in dB and the phase :math:`\phi` is in degrees.
"""
if deg:
phase = np.deg2rad(phase)
if dBm:
s21_complex = 10 ** (mag / 20) * np.exp(1j * phase)
else:
s21_complex = mag * np.exp(1j * phase)
return s21_complex.real, s21_complex.imag
[docs]
def convert_complex_to_magphase(
real: NDArray, imag: NDArray, deg: bool = True
) -> tuple[NDArray, NDArray]:
r"""
Converts real and imaginary data into magnitude (dBm) and phase.
Parameters
----------
real : NDArray
Real data array.
imag : NDArray
Imaginary data array.
deg : bool, optional
If ``True`` the phase is returned in degrees. Defaults to ``True``.
Notes
-----
This conversion is defined as
.. math::
|S_{21}|=20\cdot\log_{10}\sqrt{\mathrm{Re}(S_{21})^2+\mathrm{Im}(S_{21})^2}
\phi=\arctan\left(\frac{\mathrm{Im}(S_{21})}{\mathrm{Re}(S_{21})}\right)
"""
phase = np.angle(real + 1j * imag, deg=deg)
mag = 20 * np.log10(np.sqrt(real**2 + imag**2))
return mag, phase
def load_graph_data(path: str) -> dict[str, NDArray]:
"""
Loads the data saved in csv files by the Grapher objects and returns it
as a dictionnary with label as key and NDArrays of the data.
Parameters
----------
path : str
Complete file file path of the file containing the data.
"""
df = pd.read_csv(path, header=[0, 1], index_col=0)
loaded_data = {}
for label in df.columns.get_level_values(0).unique():
loaded_data[label] = np.array(df[label]).T
return loaded_data
def level_phase(phase: ArrayLike, deg: bool = False) -> ArrayLike:
"""
Levels the phase by substracting the slope.
"""
unwrapped_phase = np.unwrap(phase, 180) if deg else np.unwrap(phase)
pointA = unwrapped_phase[0]
pointB = unwrapped_phase[-1]
slope = np.linspace(pointA, pointB, len(unwrapped_phase))
return unwrapped_phase - slope