diff --git a/blowback/LICENSE.md b/backwash/LICENSE.md similarity index 100% rename from blowback/LICENSE.md rename to backwash/LICENSE.md diff --git a/blowback/README.md b/backwash/README.md similarity index 100% rename from blowback/README.md rename to backwash/README.md diff --git a/blowback/__init__.py b/backwash/__init__.py similarity index 100% rename from blowback/__init__.py rename to backwash/__init__.py diff --git a/blowback/obr.py b/backwash/obr.py similarity index 98% rename from blowback/obr.py rename to backwash/obr.py index 181e9c4..27725b4 100644 --- a/blowback/obr.py +++ b/backwash/obr.py @@ -79,7 +79,7 @@ class OBRData: metadata = fromfile(dtype=numpy.float64, offset=0x0c, count=4) ng = fromfile(dtype=numpy.float64, offset=0x2e, count=1) gain_dB = fromfile(dtype=numpy.int16, offset=0x36, count=1) - dates = fromfile(dtype=numpy.int16, offset=0x46, count=2) + dates = fromfile(dtype=numpy.int16, offset=0x46, count=2 * 8) t0half = fromfile(dtype=numpy.float64, offset=0x96, count=1) freq_windowed = fromfile(dtype=numpy.int8, offset=0xb6, count=1).any() arr = fromfile(dtype=numpy.float64, offset=0x800) @@ -92,7 +92,6 @@ class OBRData: tm = arrs[2] + 1j * arrs[3] meas_date, calib_date = numpy.split(dates, 2) - tt = numpy.arange(arrs[0].size) * dt_ns + t0half * 2 result = OBRData( te = te, diff --git a/backwash/ova_bin.py b/backwash/ova_bin.py new file mode 100644 index 0000000..caeae6a --- /dev/null +++ b/backwash/ova_bin.py @@ -0,0 +1,135 @@ +from typing import IO, Self +from dataclasses import dataclass +from pathlib import Path +import logging +import numpy +from numpy.typing import NDArray +from numpy.fft import fftshift, fftfreq + +from .utils import C0 + + +logger = logging.getLogger(__name__) + + +@dataclass +class OVABINData: + te: NDArray[numpy.complex128] # time domain complex amplitude, polarization 1 + tm: NDArray[numpy.complex128] # time domain complex amplitude, polarization 2 + dt_ns: float # delta between time points + max_freq_GHz: float # sweep's max frequency (shortest wavelength) + ng_setting: float | None # set by user (for info only) + meas_date: NDArray[numpy.int16] # 8 items: year month [week?] day hour min sec ms + _wip_metadata: NDArray[numpy.float64] | None # unknown metadata values + + @property + def time_ns(self) -> NDArray[numpy.float64]: + """ + Time delay (x-axis) for `te` and `tm` data + """ + tt = numpy.arange(self.tee.size) * self.dt_ns + return tt + + @property + def wl_range_nm(self) -> NDArray[numpy.float64]: + freq_range = numpy.asarray([ + self.max_freq_GHz - 1 / (2 * self.dt_ns), + welf.max_freq_GHz, + ]) + return C0 / freq_range + + @property + def ctr_freq_GHz(self) -> float: + return self.max_freq_GHz - 1 / (4 * self.dt_ns) + + def freqs(self, size: int) -> NDArray[numpy.float64]: + # To be used with spectrum generated with e.g. fft(fftshift(data.te)) + frq_GHz = fftshift(fftfreq(size, d=self.dt_ns)) + self.ctr_freq_GHz + return frq_GHz + + def wls(self, size: int) -> NDArray[numpy.float64]: + # To be used with spectrum generated with e.g. fft(fftshift(data.te)) + return C0 / self.freqs(size) + + def window(self, size: int) -> NDArray[numpy.float64]: + # Approximation of the generalized Hamming window used when freq_windowed==True + nn = numpy.linspace(0, 1, size) + alpha = 0.572 + scale = 1.5 # likely related to "incoherent power gain compensation" for Hamming window (=1.54) + # 1.50 seems to fit the data better though + ham = alpha - (1 - alpha) * numpy.cos(2 * numpy.pi * nn) + return scale * ham + + @staticmethod + def read(file: str | IO[bytes] | Path) -> Self: + raise NotImplementedError('Still WIP') + def fromfile(*args, **kwargs) -> NDArray: + if not isinstance(file, str | Path): + file.seek(0) + return numpy.fromfile(file, *args, **kwargs) + + magic = fromfile(dtype=numpy.uint8, offset=0x10, count=7) + if (magic != [0x40] + [ord(cc) for cc in 'S33OVA']).any(): + logger.warning(f'Unexpected magic bytes: {magic}') + + metadata = fromfile(dtype=numpy.float64, offset=0x00, count=2) + meas_date = fromfile(dtype=numpy.int16, offset=0x49, count=8) + + wl_range = fromfile(dtype=numpy.int32, offset=0x17, count=1) + data_len = 4 * wl_range + arr = fromfile(dtype=numpy.float64, offset=0x59, count=data_len) + ng = fromfile(dtype=numpy.float32, offset=0x21, count=1) + + max_freq_GHz = metadata[0] + #dt_ns = metadata[3] / 2 + + arrs = numpy.split(arr, 4) + te = arrs[0] + 1j * arrs[1] + tm = arrs[2] + 1j * arrs[3] + + result = OVABINData( + te = te, + tm = tm, + #dt_ns = dt_ns, + max_freq_GHz = max_freq_GHz, + ng_setting = ng, + meas_date = meas_date, + _wip_metadata = metadata[1:], + ) + + +""" +Notes on .bin file format (OVA) +================================ + +0x00: 9D A8 5A 6B C8 D0 0C 41 13 23 63 4D 22 74 C4 3F + |max_freq_GHZ (f64)------ | f64 ??----------------- | + C0/freq = wl_nm 5.848e6 ~> 51.3nm if GHz + could be dt in ps? + +0x10: 40 53 33 33 4F 56 41 00 00 00 01 00 00 00 00 00 + | S 3 3 | O V A | wl range | + 1 = biggest range (58.97nm), others are exponents of 2 smaller (resolution) + effectively length of each sub-array (x 4 arrays) + +0x20: 00 00 80 20 40 01 00 00 00 00 00 00 00 00 00 00 + | ng (f32) |----........| + ??? average count + +0x30: 00 00 00 00 00 00 38 98 40 00 00 00 00 00 00 00 + ??????????? + +0x40: 00 00 00 00 00 00 00 00 00 E7 07 0A 00 05 00 06 + |year month week? day- + acquisition timestamp + +0x50: 00 12 00 0F 00 0F 00 23 00 F7 27 6F E5 17 A1 A7 + --- hr minute sec ms | f64 data starts---- + (offset 0x59) + +0x400040: + 3F C8 E2 3E 89 8A A4 8C BF A9 4D A8 8A D6 9B 94 +0x400050: + 3F 8E BB 79 0C C2 84 92 3F 4E 4F 4E 45 40 + |N O N E | +""" diff --git a/blowback/py.typed b/backwash/py.typed similarity index 100% rename from blowback/py.typed rename to backwash/py.typed diff --git a/blowback/utils.py b/backwash/utils.py similarity index 100% rename from blowback/utils.py rename to backwash/utils.py diff --git a/pyproject.toml b/pyproject.toml index 4082f1e..6e2a4ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "blowback" +name = "backwash" description = "Optical reflectometry file format reader" readme = "README.md" license = { file = "LICENSE.md" } @@ -39,7 +39,7 @@ dependencies = [ "numpy>=1.26", ] [tool.hatch.version] -path = "blowback/__init__.py" +path = "backwash/__init__.py" [tool.ruff] diff --git a/uv.lock b/uv.lock index 510cc94..8771fe8 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.13" [[package]] -name = "blowback" +name = "backwash" version = "0.1.0" source = { virtual = "." } dependencies = [