Source code for pyuff_ustb.objects.channel_data
from typing import TYPE_CHECKING, List, Union
import numpy as np
from pyuff_ustb.objects.uff import (
Uff,
compulsory_property,
dependent_property,
optional_property,
)
from pyuff_ustb.readers import read_array, read_scalar, util
if TYPE_CHECKING:
from pyuff_ustb.objects.phantom import Phantom
from pyuff_ustb.objects.probes.probe import Probe
from pyuff_ustb.objects.pulse import Pulse
from pyuff_ustb.objects.wave import Wave
# Make sure properties are treated as properties when type checking
compulsory_property = property
optional_property = property
dependent_property = property
[docs]
class ChannelData(Uff):
""":class:`Uff` class to hold channel data.
:class:`ChannelData` contains raw ultrasound data as acquired from an ultrasound
scanner. Data is stored in the property :attr:`data` with dimensions:
``[time-dimension x channel-dimension x wave-dimension x frame-dimension]``.
Original authors:
Alfonso Rodriguez-Molares <alfonso.r.molares@ntnu.no>
"""
# Compulsory properties
@compulsory_property
def sampling_frequency(self) -> float:
"Sampling frequency [Hz]"
return read_scalar(self._reader["sampling_frequency"])
@compulsory_property
def initial_time(self) -> float:
"Time of the initial sample [s]"
return read_scalar(self._reader["initial_time"])
@compulsory_property
def sound_speed(self) -> float:
"Reference sound speed [m/s]"
return read_scalar(self._reader["sound_speed"])
@compulsory_property
def modulation_frequency(self) -> float:
"Modulation frequency [Hz]"
return read_scalar(self._reader["modulation_frequency"])
@compulsory_property
def sequence(self) -> Union["Wave", List["Wave"]]:
"Collection of UFF.WAVE objects"
from pyuff_ustb.objects.wave import Wave
return util.read_potentially_list(self._reader["sequence"], Wave)
@compulsory_property
def probe(self) -> "Probe":
"UFF.PROBE object"
return util.read_probe(self._reader["probe"])
@compulsory_property
def data(self) -> np.ndarray:
"Channel data [time dim. x channel dim. x wave dim. x frame dim.]"
return read_array(self._reader["data"]).T
# Optional properties
@optional_property
def pulse(self) -> "Pulse":
"UFF.PULSE object"
from pyuff_ustb.objects.pulse import Pulse
return Pulse(self._reader["pulse"])
@optional_property
def phantom(self) -> "Phantom":
"UFF.PHANTOM object"
from pyuff_ustb.objects.phantom import Phantom
return Phantom(self._reader["phantom"])
@optional_property
def PRF(self) -> float:
"Pulse repetition frequency [Hz]"
prf_key = "PRF" if "PRF" in self._reader else "prf"
return read_scalar(self._reader[prf_key])
@optional_property
def N_active_elements(self) -> int:
"Number of active transducers on receive"
return int(read_scalar(self._reader["N_active_elements"]))
# Dependent properties
@dependent_property
def N_samples(self) -> int:
"Number of samples in the data"
return self.data.shape[0]
@dependent_property
def N_elements(self) -> int:
"Number of elements in the probe"
return self.probe.N_elements
@dependent_property
def N_channels(self) -> int:
"Number of elements in the probe"
return self.probe.N_elements
@dependent_property
def N_waves(self) -> int:
"Number of transmitted waves"
from pyuff_ustb.objects.wave import Wave
if isinstance(self.sequence, list):
return len(self.sequence)
if isinstance(self.sequence, Wave):
return 1
return 0
@dependent_property
def N_frames(self) -> int:
"Number of frames"
if self.data.ndim == 4:
return self.data.shape[3]
return 1
@dependent_property
def wavelength(self) -> float:
"""Wavelength [m]
Same as ChannelData.lambda in USTB, but lambda is a reserved keyword in Python.
"""
assert (
self.sound_speed is not None
), "You need to set the channel_data.sound_speed"
assert (
self.pulse is not None
and self.pulse.center_frequency is not None
and self.pulse.center_frequency != 0
), "You need to set the pulse and the pulse center frequency."
return self.sound_speed / self.pulse.center_frequency
def _preprocess_write(self, name: str, value):
if name == "data":
return value.T
return value