Source code for openlifu.xdc.transducerarray

from __future__ import annotations

import json
from dataclasses import dataclass, field

import numpy as np

from openlifu.util.dict_conversion import DictMixin
from openlifu.util.units import getunitconversion
from openlifu.xdc import Transducer, TransformedTransducer


[docs] def get_angle_from_gap(width, gap, roc): a = roc b = width/2 c = gap/2 mag = np.sqrt(a**2 + b**2) A = a/mag B = b/mag dth = np.arcsin(c/mag) + np.arcsin(B) return dth if A >= 0 else -dth
[docs] def get_roc_from_angle(width, gap, dth): return (0.5*gap + (0.5 * width * np.cos(dth))) / np.sin(dth)
[docs] def get_gap_from_angle(width, dth, roc): a = roc b = width/2 mag = np.sqrt(a**2 + b**2) A = a/mag B = b/mag gap = 2*mag*np.sin(dth - np.arcsin(B)) return gap if A >= 0 else -gap
[docs] @dataclass class TransducerArray(DictMixin): id: str = "transducer_array" name: str = "Transducer Array" modules: list[TransformedTransducer] = field(default_factory=list) attrs: dict = field(default_factory=dict) def to_transducer(self, offset_pins=True, offset_indices=True): t = Transducer.merge([t.bake() for t in self.modules], offset_pins=offset_pins, offset_indices=offset_indices, merged_attrs=self.attrs) t.name = self.name t.id = self.id return t
[docs] @staticmethod def from_dict(data: dict): d = data.copy() if "type" in d: d.pop("type") d["modules"] = [TransformedTransducer.from_dict(t) for t in data["modules"]] if "attrs" in d: if "standoff_transform" in d["attrs"] and d["attrs"]["standoff_transform"] is not None: d["attrs"]["standoff_transform"] = np.array(d["attrs"]["standoff_transform"]) d["attrs"].pop("impulse_response", None) d["attrs"].pop("impulse_dt", None) return TransducerArray(**d)
[docs] def to_dict(self): d = {"type": "TransducerArray"} d.update(self.__dict__) d["modules"] = [t.to_dict() for t in self.modules] for k, v in self.attrs.items(): if isinstance(v, np.ndarray): d["attrs"][k] = v.tolist() return d
[docs] def to_json(self, compact:bool=False) -> str: """Serialize a TransducerArray to a json string Args: compact: if enabled then the string is compact (not pretty). Disable for pretty. Returns: A json string representing the complete TransducerArray object. """ if compact: return json.dumps(self.to_dict(), separators=(',', ':')) else: return json.dumps(self.to_dict(), indent=4)
[docs] def to_file(self, file_path: str, compact: bool = False) -> None: """Serialize a TransducerArray to a json file Args: file_path: The path to the file where the json string will be written. compact: if enabled then the string is compact (not pretty). Disable for pretty. """ json_string = self.to_json(compact=compact) with open(file_path, 'w') as f: f.write(json_string)
@staticmethod def get_concave_cylinder(trans, rows=1, cols=1, width=40, gap=None, dth=None, roc=None, units="mm", id="transducer_array", name="Transducer Array", attrs: dict={}): modules = [] if isinstance(trans, Transducer): trans_arr = np.array([[trans]*cols for _ in range(rows)]) else: trans_arr = np.array(trans).reshape(rows, cols) scl = getunitconversion(units, trans_arr[0,0].units) if gap is None: if dth is not None and roc is not None: gap = get_gap_from_angle(width, dth, roc) else: gap = 0 elif dth is not None and roc is not None: raise ValueError("Invalid combination of parameters: cannot specify all of gap, dth, and roc.") if dth is None: if roc is not None: dth = get_angle_from_gap(width, gap, roc) else: dth = 0 roc = np.inf if roc is None: if np.isclose(dth, 0.0): roc = np.inf else: roc = get_roc_from_angle(width, gap, dth) if dth == 0: for i in range(rows): y = (width+gap)*(i-(rows-1)/2)*scl for j in range(cols): dx = (width+gap)*(j-(cols-1)/2)*scl M = np.array([[1,0,0,dx], [0,1,0,y], [0,0,1,0], [0,0,0,1]]) trans_new = TransformedTransducer.from_transducer(trans_arr[i,j], transform=np.linalg.inv(M)) modules.append(trans_new) else: for i in range(rows): y = (width+gap)*(i-(rows-1)/2)*scl for j in range(cols): th = dth*2*(j-(cols-1)/2) x = roc*np.sin(th)*scl z = roc*(1-np.cos(th))*scl M = np.array([[np.cos(th),0,-np.sin(th),x], [0,1,0,y], [np.sin(th),0,np.cos(th),z], [0,0,0,1]]) trans_new = TransformedTransducer.from_transducer(trans_arr[i,j], transform=np.linalg.inv(M)) modules.append(trans_new) return TransducerArray(modules=modules, id=id, name=name, attrs=attrs) @staticmethod def from_file(filename: str) -> TransducerArray: with open(filename) as f: data = json.load(f) return TransducerArray.from_dict(data) @property def registration_surface_filename(self): if "registration_surface_filename" in self.attrs: return self.attrs["registration_surface_filename"] return None @registration_surface_filename.setter def registration_surface_filename(self, value): self.attrs["registration_surface_filename"] = value @property def transducer_body_filename(self): if "transducer_body_filename" in self.attrs: return self.attrs["transducer_body_filename"] return None @transducer_body_filename.setter def transducer_body_filename(self, value): self.attrs["transducer_body_filename"] = value