Source code for pyuff_ustb.objects.scans.sector_scan

from typing import TYPE_CHECKING, List, Union

import numpy as np

from pyuff_ustb.objects.scans.scan import Scan
from pyuff_ustb.objects.uff import compulsory_property, dependent_property
from pyuff_ustb.readers import read_array, util

if TYPE_CHECKING:
    from pyuff_ustb.objects.point import Point

    # Make sure properties are treated as properties when type checking
    compulsory_property = property
    optional_property = property
    dependent_property = property


[docs] class SectorScan(Scan): """:class:`Uff` class to define a sector scan. :class:`SectorScan` contains the position of the azimuth and depth axis from an origin. The origin may be a single point or a list of points with the same length as the ``azimuth_axis``. In the case of multiple origins, each origin represents the apex of a single azimuth direction/column of the scan. Original authors: * Alfonso Rodriguez-Molares <alfonso.r.molares@ntnu.no> * Anders E. Vrålstad <anders.e.vralstad@ntnu.no> * Stefano Fiorentini <stefano.fiorentini@ntnu.no> """ # Compulsory properties @compulsory_property def azimuth_axis(self) -> np.ndarray: "Vector containing the azimuth coordinates [rad]" return read_array(self._reader["azimuth_axis"]) @compulsory_property def depth_axis(self) -> np.ndarray: "Vector containing the distance coordinates [m]" return read_array(self._reader["depth_axis"]) @compulsory_property def origin(self) -> Union["Point", List["Point"]]: "Vector of UFF.POINT objects" from pyuff_ustb.objects.point import Point if "origin" in self._reader: return util.read_potentially_list(self._reader["origin"], Point) if "apex" in self._reader: return Point(self._reader["apex"]) # Dependent properties @dependent_property def N_azimuth_axis(self) -> int: "Number of pixels in azimuth_axis" return len(self.azimuth_axis) @dependent_property def N_depth_axis(self) -> int: "Number of pixels in depth_axis" return len(self.depth_axis) @dependent_property def N_origins(self) -> int: "Number of scanline origins" if isinstance(self.origin, (list, tuple)): return len(self.origin) return 1 @dependent_property def depth_step(self) -> float: "Step size along the depth axis [m]" return np.mean(np.diff(self.depth_axis)) @dependent_property def reference_distance(self): "Distance used for the calculation of the phase term [m]" raise NotImplementedError("Create an issue on the repository if you need this.") # Override some compulsory properties of Scan @compulsory_property def x(self) -> np.ndarray: # Try to read x from the file first if "x" in self._reader: return read_array(self._reader["x"]) # If x is not set in the file, calculate it based on the fields. if ( (self.azimuth_axis is None) or (self.depth_axis is None) or (self.origin is None) ): raise ValueError( "Cannot calculate x without azimuth_axis, depth_axis, and origin" ) rho, theta = np.meshgrid(self.depth_axis, self.azimuth_axis, indexing="ij") N_pixels = rho.size return np.reshape(rho * np.sin(theta) + self.origin.x, [N_pixels]) @compulsory_property def y(self) -> np.ndarray: # Try to read y from the file first if "y" in self._reader: return read_array(self._reader["y"]) # If y is not set in the file, calculate it based on the fields. if ( (self.azimuth_axis is None) or (self.depth_axis is None) or (self.origin is None) ): raise ValueError( "Cannot calculate y without azimuth_axis, depth_axis, and origin" ) rho, theta = np.meshgrid(self.depth_axis, self.azimuth_axis, indexing="ij") N_pixels = rho.size return np.reshape(np.zeros(rho.shape) + self.origin.y, [N_pixels]) @compulsory_property def z(self) -> np.ndarray: # Try to read z from the file first if "z" in self._reader: return read_array(self._reader["z"]) # If z is not set in the file, calculate it based on the fields. if ( (self.azimuth_axis is None) or (self.depth_axis is None) or (self.origin is None) ): raise ValueError( "Cannot calculate z without azimuth_axis, depth_axis, and origin" ) rho, theta = np.meshgrid(self.depth_axis, self.azimuth_axis, indexing="ij") N_pixels = rho.size return np.reshape(rho * np.cos(theta) + self.origin.z, [N_pixels])